From dfffad9aa55d4ec539e8907218d89c260c4f730b Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Fri, 20 Feb 2026 14:29:08 +0000 Subject: [PATCH 01/14] [PRMP-1409] feat: add Document Version History feature - Implemented DocumentVersionHistoryPage to display version history of documents. - Created Timeline component to visualize document versions with active/inactive states. - Added getDocumentVersionHistory function to fetch version history from API. - Introduced mock data for testing document version history responses. - Developed styles for TestToggle component to enhance UI. - Added unit tests for getDocumentVersionHistoryResponse to ensure correct API interaction and response structure. --- app/main.html | 2 + app/package.json | 1 - app/public/nhsapp-5.0.3.min.css | 1 + .../components/blocks/testPanel/TestPanel.tsx | 9 ++- .../blocks/testPanel/TestToggle.tsx | 21 ------ .../components/blocks/testPanel/Toggle.scss | 71 +++++++++++++++++++ .../components/blocks/testPanel/Toggle.tsx | 28 ++++++++ app/src/types/generic/fhir.ts | 11 +++ 8 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 app/public/nhsapp-5.0.3.min.css delete mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx create mode 100644 app/src/components/blocks/testPanel/Toggle.scss create mode 100644 app/src/components/blocks/testPanel/Toggle.tsx create mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index b6f9a302ce..f45a5f122a 100644 --- a/app/main.html +++ b/app/main.html @@ -49,6 +49,8 @@ + + diff --git a/app/package.json b/app/package.json index 846023c84b..263225af58 100644 --- a/app/package.json +++ b/app/package.json @@ -78,7 +78,6 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css new file mode 100644 index 0000000000..f482ed553c --- /dev/null +++ b/app/public/nhsapp-5.0.3.min.css @@ -0,0 +1 @@ +.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index 35869008b4..32eb7d51a7 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,9 +1,8 @@ -import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; import { FeatureFlags } from '../../../types/generic/featureFlags'; -import TestToggle, { ToggleProps } from './TestToggle'; +import Toggle, { ToggleProps } from './Toggle'; const TestPanel = (): React.JSX.Element => { const [config, setConfig] = useConfigContext(); @@ -112,7 +111,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Data

@@ -121,10 +120,10 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Feature Flags

- void; -}; - -const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- - -
- ); -}; - -export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss new file mode 100644 index 0000000000..954bea161b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.scss @@ -0,0 +1,71 @@ +$toggle-track-color: #adb5bd; +$toggle-checked-color: #007f3b; +$toggle-focus-color: #ffb81c; +$toggle-knob-color: #fff; + +.ndr-toggle-div { + margin-bottom: 8px; +} + +.ndr-toggle-label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + margin: 0; +} + +// Hide the native checkbox +.ndr-toggle-input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + + // Checked state + &:checked + .ndr-toggle-track { + background-color: $toggle-checked-color; + + &::after { + transform: translateX(22px); + } + } + + // Focus ring for accessibility + &:focus-visible + .ndr-toggle-track { + outline: 3px solid $toggle-focus-color; + outline-offset: 2px; + } +} + +// The track +.ndr-toggle-track { + position: relative; + display: inline-block; + width: 48px; + height: 26px; + background-color: $toggle-track-color; + border-radius: 13px; + cursor: pointer; + flex-shrink: 0; + transition: background-color 0.2s ease; + + // The knob + &::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + background-color: $toggle-knob-color; + border-radius: 50%; + transition: transform 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } +} + +.ndr-toggle-paragraph { + margin: 0; + font-size: 14px; +} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx new file mode 100644 index 0000000000..a71ed3224b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.tsx @@ -0,0 +1,28 @@ +import './Toggle.scss'; + +export type ToggleProps = { + id: string; + checked: boolean; + label: string; + onChange: () => void; +}; + +const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ +
+ ); +}; + +export default Toggle; diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts new file mode 100644 index 0000000000..69484b1924 --- /dev/null +++ b/app/src/types/generic/fhir.ts @@ -0,0 +1,11 @@ +export type Bundle = { + resourceType: string; + type: string; + total: number; + entry: Array>; +}; + +export type BundleEntry = { + fullUrl?: string; + resource: T; +}; From d3e67ffe1b5cbd1ec48a2f2d8db66e2ae5f9902e Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Wed, 4 Mar 2026 18:34:54 +0000 Subject: [PATCH 02/14] [PRMP-1409] feat: Add FHIR R4 Bundle and DocumentReference resources - Implemented the FHIR R4 Bundle resource with associated enums and interfaces. - Created sample JSON files for Bundle history (bundleHistory1.fhir.json and bundleHistory2.fhir.json). - Developed the FHIR R4 DocumentReference resource with its structure and value sets. - Added unit tests for validating the structure and data of Bundle and DocumentReference resources. - Removed outdated generic FHIR types and updated routes for document version history. --- app/main.html | 2 - app/package.json | 1 + app/public/nhsapp-5.0.3.min.css | 1 - .../components/blocks/testPanel/TestPanel.tsx | 9 +-- .../blocks/testPanel/TestToggle.tsx | 21 ++++++ .../components/blocks/testPanel/Toggle.scss | 71 ------------------- .../components/blocks/testPanel/Toggle.tsx | 28 -------- app/src/types/generic/fhir.ts | 11 --- 8 files changed, 27 insertions(+), 117 deletions(-) delete mode 100644 app/public/nhsapp-5.0.3.min.css create mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx delete mode 100644 app/src/components/blocks/testPanel/Toggle.scss delete mode 100644 app/src/components/blocks/testPanel/Toggle.tsx delete mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index f45a5f122a..b6f9a302ce 100644 --- a/app/main.html +++ b/app/main.html @@ -49,8 +49,6 @@ - - diff --git a/app/package.json b/app/package.json index 263225af58..846023c84b 100644 --- a/app/package.json +++ b/app/package.json @@ -78,6 +78,7 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css deleted file mode 100644 index f482ed553c..0000000000 --- a/app/public/nhsapp-5.0.3.min.css +++ /dev/null @@ -1 +0,0 @@ -.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index 32eb7d51a7..35869008b4 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,8 +1,9 @@ +import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; import { FeatureFlags } from '../../../types/generic/featureFlags'; -import Toggle, { ToggleProps } from './Toggle'; +import TestToggle, { ToggleProps } from './TestToggle'; const TestPanel = (): React.JSX.Element => { const [config, setConfig] = useConfigContext(); @@ -111,7 +112,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Data

@@ -120,10 +121,10 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Feature Flags

- void; +}; + +const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ + +
+ ); +}; + +export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss deleted file mode 100644 index 954bea161b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.scss +++ /dev/null @@ -1,71 +0,0 @@ -$toggle-track-color: #adb5bd; -$toggle-checked-color: #007f3b; -$toggle-focus-color: #ffb81c; -$toggle-knob-color: #fff; - -.ndr-toggle-div { - margin-bottom: 8px; -} - -.ndr-toggle-label { - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - margin: 0; -} - -// Hide the native checkbox -.ndr-toggle-input { - position: absolute; - opacity: 0; - width: 0; - height: 0; - - // Checked state - &:checked + .ndr-toggle-track { - background-color: $toggle-checked-color; - - &::after { - transform: translateX(22px); - } - } - - // Focus ring for accessibility - &:focus-visible + .ndr-toggle-track { - outline: 3px solid $toggle-focus-color; - outline-offset: 2px; - } -} - -// The track -.ndr-toggle-track { - position: relative; - display: inline-block; - width: 48px; - height: 26px; - background-color: $toggle-track-color; - border-radius: 13px; - cursor: pointer; - flex-shrink: 0; - transition: background-color 0.2s ease; - - // The knob - &::after { - content: ''; - position: absolute; - top: 3px; - left: 3px; - width: 20px; - height: 20px; - background-color: $toggle-knob-color; - border-radius: 50%; - transition: transform 0.2s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - } -} - -.ndr-toggle-paragraph { - margin: 0; - font-size: 14px; -} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx deleted file mode 100644 index a71ed3224b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import './Toggle.scss'; - -export type ToggleProps = { - id: string; - checked: boolean; - label: string; - onChange: () => void; -}; - -const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- -
- ); -}; - -export default Toggle; diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts deleted file mode 100644 index 69484b1924..0000000000 --- a/app/src/types/generic/fhir.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type Bundle = { - resourceType: string; - type: string; - total: number; - entry: Array>; -}; - -export type BundleEntry = { - fullUrl?: string; - resource: T; -}; From 63b789cd5e9b46cd64cd0124e87f0faade508040 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Fri, 20 Feb 2026 14:29:08 +0000 Subject: [PATCH 03/14] [PRMP-1409] feat: add Document Version History feature - Implemented DocumentVersionHistoryPage to display version history of documents. - Created Timeline component to visualize document versions with active/inactive states. - Added getDocumentVersionHistory function to fetch version history from API. - Introduced mock data for testing document version history responses. - Developed styles for TestToggle component to enhance UI. - Added unit tests for getDocumentVersionHistoryResponse to ensure correct API interaction and response structure. --- app/main.html | 2 + app/package.json | 1 - app/public/nhsapp-5.0.3.min.css | 1 + .../components/blocks/testPanel/TestPanel.tsx | 5 +- .../blocks/testPanel/TestToggle.tsx | 21 ------ .../components/blocks/testPanel/Toggle.scss | 71 +++++++++++++++++++ .../components/blocks/testPanel/Toggle.tsx | 28 ++++++++ app/src/types/generic/fhir.ts | 11 +++ app/src/types/generic/routes.ts | 2 + 9 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 app/public/nhsapp-5.0.3.min.css delete mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx create mode 100644 app/src/components/blocks/testPanel/Toggle.scss create mode 100644 app/src/components/blocks/testPanel/Toggle.tsx create mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index b6f9a302ce..f45a5f122a 100644 --- a/app/main.html +++ b/app/main.html @@ -49,6 +49,8 @@ + + diff --git a/app/package.json b/app/package.json index 846023c84b..263225af58 100644 --- a/app/package.json +++ b/app/package.json @@ -78,7 +78,6 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css new file mode 100644 index 0000000000..f482ed553c --- /dev/null +++ b/app/public/nhsapp-5.0.3.min.css @@ -0,0 +1 @@ +.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index 35869008b4..db1e7c6dd2 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,4 +1,3 @@ -import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; @@ -112,7 +111,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Data

@@ -121,7 +120,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Feature Flags

void; -}; - -const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- - -
- ); -}; - -export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss new file mode 100644 index 0000000000..954bea161b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.scss @@ -0,0 +1,71 @@ +$toggle-track-color: #adb5bd; +$toggle-checked-color: #007f3b; +$toggle-focus-color: #ffb81c; +$toggle-knob-color: #fff; + +.ndr-toggle-div { + margin-bottom: 8px; +} + +.ndr-toggle-label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + margin: 0; +} + +// Hide the native checkbox +.ndr-toggle-input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + + // Checked state + &:checked + .ndr-toggle-track { + background-color: $toggle-checked-color; + + &::after { + transform: translateX(22px); + } + } + + // Focus ring for accessibility + &:focus-visible + .ndr-toggle-track { + outline: 3px solid $toggle-focus-color; + outline-offset: 2px; + } +} + +// The track +.ndr-toggle-track { + position: relative; + display: inline-block; + width: 48px; + height: 26px; + background-color: $toggle-track-color; + border-radius: 13px; + cursor: pointer; + flex-shrink: 0; + transition: background-color 0.2s ease; + + // The knob + &::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + background-color: $toggle-knob-color; + border-radius: 50%; + transition: transform 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } +} + +.ndr-toggle-paragraph { + margin: 0; + font-size: 14px; +} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx new file mode 100644 index 0000000000..a71ed3224b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.tsx @@ -0,0 +1,28 @@ +import './Toggle.scss'; + +export type ToggleProps = { + id: string; + checked: boolean; + label: string; + onChange: () => void; +}; + +const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ +
+ ); +}; + +export default Toggle; diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts new file mode 100644 index 0000000000..69484b1924 --- /dev/null +++ b/app/src/types/generic/fhir.ts @@ -0,0 +1,11 @@ +export type Bundle = { + resourceType: string; + type: string; + total: number; + entry: Array>; +}; + +export type BundleEntry = { + fullUrl?: string; + resource: T; +}; diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts index 826b733920..87f16d8958 100644 --- a/app/src/types/generic/routes.ts +++ b/app/src/types/generic/routes.ts @@ -77,6 +77,8 @@ export enum routeChildren { DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', + DOCUMENT_VERSION_HISTORY = '/patient/document-version-history', + DOCUMENT_VIEW = '/patient/documents/view', DOCUMENT_DELETE = '/patient/documents/delete', DOCUMENT_DELETE_CONFIRMATION = '/patient/documents/delete/confirmation', From 28eef18e6939d0997c96e216e1dc748a0f460922 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Wed, 4 Mar 2026 18:34:54 +0000 Subject: [PATCH 04/14] [PRMP-1409] feat: Add FHIR R4 Bundle and DocumentReference resources - Implemented the FHIR R4 Bundle resource with associated enums and interfaces. - Created sample JSON files for Bundle history (bundleHistory1.fhir.json and bundleHistory2.fhir.json). - Developed the FHIR R4 DocumentReference resource with its structure and value sets. - Added unit tests for validating the structure and data of Bundle and DocumentReference resources. - Removed outdated generic FHIR types and updated routes for document version history. --- app/main.html | 2 - app/package.json | 1 + app/public/nhsapp-5.0.3.min.css | 1 - .../components/blocks/testPanel/TestPanel.tsx | 5 +- .../blocks/testPanel/TestToggle.tsx | 21 ++++++ .../components/blocks/testPanel/Toggle.scss | 71 ------------------- .../components/blocks/testPanel/Toggle.tsx | 28 -------- app/src/types/generic/fhir.ts | 11 --- app/src/types/generic/routes.ts | 3 +- 9 files changed, 27 insertions(+), 116 deletions(-) delete mode 100644 app/public/nhsapp-5.0.3.min.css create mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx delete mode 100644 app/src/components/blocks/testPanel/Toggle.scss delete mode 100644 app/src/components/blocks/testPanel/Toggle.tsx delete mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index f45a5f122a..b6f9a302ce 100644 --- a/app/main.html +++ b/app/main.html @@ -49,8 +49,6 @@ - - diff --git a/app/package.json b/app/package.json index 263225af58..846023c84b 100644 --- a/app/package.json +++ b/app/package.json @@ -78,6 +78,7 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css deleted file mode 100644 index f482ed553c..0000000000 --- a/app/public/nhsapp-5.0.3.min.css +++ /dev/null @@ -1 +0,0 @@ -.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index db1e7c6dd2..35869008b4 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,3 +1,4 @@ +import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; @@ -111,7 +112,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Data

@@ -120,7 +121,7 @@ const TestPanel = (): React.JSX.Element => { id, ...value, }; - return ; + return ; })}

Feature Flags

void; +}; + +const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ + +
+ ); +}; + +export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss deleted file mode 100644 index 954bea161b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.scss +++ /dev/null @@ -1,71 +0,0 @@ -$toggle-track-color: #adb5bd; -$toggle-checked-color: #007f3b; -$toggle-focus-color: #ffb81c; -$toggle-knob-color: #fff; - -.ndr-toggle-div { - margin-bottom: 8px; -} - -.ndr-toggle-label { - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - margin: 0; -} - -// Hide the native checkbox -.ndr-toggle-input { - position: absolute; - opacity: 0; - width: 0; - height: 0; - - // Checked state - &:checked + .ndr-toggle-track { - background-color: $toggle-checked-color; - - &::after { - transform: translateX(22px); - } - } - - // Focus ring for accessibility - &:focus-visible + .ndr-toggle-track { - outline: 3px solid $toggle-focus-color; - outline-offset: 2px; - } -} - -// The track -.ndr-toggle-track { - position: relative; - display: inline-block; - width: 48px; - height: 26px; - background-color: $toggle-track-color; - border-radius: 13px; - cursor: pointer; - flex-shrink: 0; - transition: background-color 0.2s ease; - - // The knob - &::after { - content: ''; - position: absolute; - top: 3px; - left: 3px; - width: 20px; - height: 20px; - background-color: $toggle-knob-color; - border-radius: 50%; - transition: transform 0.2s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - } -} - -.ndr-toggle-paragraph { - margin: 0; - font-size: 14px; -} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx deleted file mode 100644 index a71ed3224b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import './Toggle.scss'; - -export type ToggleProps = { - id: string; - checked: boolean; - label: string; - onChange: () => void; -}; - -const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- -
- ); -}; - -export default Toggle; diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts deleted file mode 100644 index 69484b1924..0000000000 --- a/app/src/types/generic/fhir.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type Bundle = { - resourceType: string; - type: string; - total: number; - entry: Array>; -}; - -export type BundleEntry = { - fullUrl?: string; - resource: T; -}; diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts index 87f16d8958..4a07d5cf18 100644 --- a/app/src/types/generic/routes.ts +++ b/app/src/types/generic/routes.ts @@ -77,7 +77,8 @@ export enum routeChildren { DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', - DOCUMENT_VERSION_HISTORY = '/patient/document-version-history', + DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', + DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', DOCUMENT_VIEW = '/patient/documents/view', DOCUMENT_DELETE = '/patient/documents/delete', From 4120cd632f3908bec66f25308da695ef800cf222 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Fri, 20 Feb 2026 14:29:08 +0000 Subject: [PATCH 05/14] [PRMP-1409] feat: add Document Version History feature - Implemented DocumentVersionHistoryPage to display version history of documents. - Created Timeline component to visualize document versions with active/inactive states. - Added getDocumentVersionHistory function to fetch version history from API. - Introduced mock data for testing document version history responses. - Developed styles for TestToggle component to enhance UI. - Added unit tests for getDocumentVersionHistoryResponse to ensure correct API interaction and response structure. --- app/main.html | 2 + app/package.json | 1 - app/public/nhsapp-5.0.3.min.css | 1 + .../documentView/DocumentView.tsx | 5 +- .../components/blocks/testPanel/TestPanel.tsx | 3 +- .../blocks/testPanel/TestToggle.tsx | 21 ------ .../components/blocks/testPanel/Toggle.scss | 71 +++++++++++++++++++ .../components/blocks/testPanel/Toggle.tsx | 28 ++++++++ app/src/types/generic/fhir.ts | 11 +++ 9 files changed, 117 insertions(+), 26 deletions(-) create mode 100644 app/public/nhsapp-5.0.3.min.css delete mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx create mode 100644 app/src/components/blocks/testPanel/Toggle.scss create mode 100644 app/src/components/blocks/testPanel/Toggle.tsx create mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index b6f9a302ce..f45a5f122a 100644 --- a/app/main.html +++ b/app/main.html @@ -49,6 +49,8 @@ + + diff --git a/app/package.json b/app/package.json index 846023c84b..263225af58 100644 --- a/app/package.json +++ b/app/package.json @@ -78,7 +78,6 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css new file mode 100644 index 0000000000..f482ed553c --- /dev/null +++ b/app/public/nhsapp-5.0.3.min.css @@ -0,0 +1 @@ +.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx index 82007a4b89..9f9049e88b 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx @@ -259,10 +259,11 @@ const DocumentView = ({ }, 0); }; - const GetRecordCard = (): React.JSX.Element => { + const getRecordCard = (): React.JSX.Element => { const heading = documentConfig.content.getValueFormatString('viewDocumentTitle', { version: documentReference.version, })!; + const card = ( - {documentReference.url ? : } + {documentReference.url ? getRecordCard() : } ); diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index 35869008b4..907c4fe6f0 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,9 +1,8 @@ -import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; import { FeatureFlags } from '../../../types/generic/featureFlags'; -import TestToggle, { ToggleProps } from './TestToggle'; +import TestToggle, { ToggleProps } from './Toggle'; const TestPanel = (): React.JSX.Element => { const [config, setConfig] = useConfigContext(); diff --git a/app/src/components/blocks/testPanel/TestToggle.tsx b/app/src/components/blocks/testPanel/TestToggle.tsx deleted file mode 100644 index 3d2b9e0e3a..0000000000 --- a/app/src/components/blocks/testPanel/TestToggle.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Toggle from 'react-toggle'; - -export type ToggleProps = { - id: string; - checked: boolean; - label: string; - onChange: () => void; -}; - -const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- - -
- ); -}; - -export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss new file mode 100644 index 0000000000..954bea161b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.scss @@ -0,0 +1,71 @@ +$toggle-track-color: #adb5bd; +$toggle-checked-color: #007f3b; +$toggle-focus-color: #ffb81c; +$toggle-knob-color: #fff; + +.ndr-toggle-div { + margin-bottom: 8px; +} + +.ndr-toggle-label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + margin: 0; +} + +// Hide the native checkbox +.ndr-toggle-input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + + // Checked state + &:checked + .ndr-toggle-track { + background-color: $toggle-checked-color; + + &::after { + transform: translateX(22px); + } + } + + // Focus ring for accessibility + &:focus-visible + .ndr-toggle-track { + outline: 3px solid $toggle-focus-color; + outline-offset: 2px; + } +} + +// The track +.ndr-toggle-track { + position: relative; + display: inline-block; + width: 48px; + height: 26px; + background-color: $toggle-track-color; + border-radius: 13px; + cursor: pointer; + flex-shrink: 0; + transition: background-color 0.2s ease; + + // The knob + &::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + background-color: $toggle-knob-color; + border-radius: 50%; + transition: transform 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } +} + +.ndr-toggle-paragraph { + margin: 0; + font-size: 14px; +} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx new file mode 100644 index 0000000000..a71ed3224b --- /dev/null +++ b/app/src/components/blocks/testPanel/Toggle.tsx @@ -0,0 +1,28 @@ +import './Toggle.scss'; + +export type ToggleProps = { + id: string; + checked: boolean; + label: string; + onChange: () => void; +}; + +const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ +
+ ); +}; + +export default Toggle; diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts new file mode 100644 index 0000000000..69484b1924 --- /dev/null +++ b/app/src/types/generic/fhir.ts @@ -0,0 +1,11 @@ +export type Bundle = { + resourceType: string; + type: string; + total: number; + entry: Array>; +}; + +export type BundleEntry = { + fullUrl?: string; + resource: T; +}; From f32bd68a3130afab64693113f9b81279ed768e7d Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Wed, 4 Mar 2026 18:34:54 +0000 Subject: [PATCH 06/14] [PRMP-1410] Document version history view --- app/main.html | 2 - app/package.json | 1 + app/public/nhsapp-5.0.3.min.css | 1 - .../documentView/DocumentView.test.tsx | 28 ++- .../documentView/DocumentView.tsx | 92 ++++++--- .../components/blocks/testPanel/TestPanel.tsx | 3 +- .../blocks/testPanel/TestToggle.tsx | 21 ++ .../components/blocks/testPanel/Toggle.scss | 71 ------- .../components/blocks/testPanel/Toggle.tsx | 28 --- app/src/config/lloydGeorgeConfig.json | 4 +- app/src/helpers/requests/getReviews.ts | 8 +- app/src/helpers/test/getMockVersionHistory.ts | 6 +- app/src/helpers/utils/documentType.ts | 4 +- app/src/helpers/utils/fhirUtil.test.ts | 190 +++++++++++++++++- app/src/helpers/utils/fhirUtil.ts | 31 +++ app/src/helpers/utils/getPdfObjectUrl.ts | 11 +- .../DocumentSearchResultsPage.tsx | 20 +- .../DocumentVersionHistoryPage.test.tsx | 166 ++++++++++++++- .../DocumentVersionHistoryPage.tsx | 131 +++++++++--- app/src/types/blocks/lloydGeorgeActions.ts | 2 +- app/src/types/fhirR4/bundle.ts | 4 - app/src/types/generic/fhir.ts | 11 - 22 files changed, 640 insertions(+), 195 deletions(-) delete mode 100644 app/public/nhsapp-5.0.3.min.css create mode 100644 app/src/components/blocks/testPanel/TestToggle.tsx delete mode 100644 app/src/components/blocks/testPanel/Toggle.scss delete mode 100644 app/src/components/blocks/testPanel/Toggle.tsx delete mode 100644 app/src/types/generic/fhir.ts diff --git a/app/main.html b/app/main.html index f45a5f122a..b6f9a302ce 100644 --- a/app/main.html +++ b/app/main.html @@ -49,8 +49,6 @@ - - diff --git a/app/package.json b/app/package.json index 263225af58..846023c84b 100644 --- a/app/package.json +++ b/app/package.json @@ -78,6 +78,7 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "@types/react-toggle": "^4.0.5", "@types/sinon": "^21.0.0", "@types/uuid": "^11.0.0", "@types/validator": "^13.15.10", diff --git a/app/public/nhsapp-5.0.3.min.css b/app/public/nhsapp-5.0.3.min.css deleted file mode 100644 index f482ed553c..0000000000 --- a/app/public/nhsapp-5.0.3.min.css +++ /dev/null @@ -1 +0,0 @@ -.nhsapp-icon{fill:#005eb8}.nhsapp-icon--unread-indicator{fill:#d5281b;stroke:#fff}.nhsapp-icon--black{fill:#212b32}.nhsapp-icon--32{height:32px;width:32px}@media (max-width:40.0525em){.nhsapp-icon--32{height:24px;width:24px}}.nhsapp-badge{display:inline-block;background-color:#d5281b;border-radius:4px;color:#fff;font-weight:700;padding:0 8px;margin:0}.nhsapp-badge{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-badge{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-badge{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-badge{padding:0 12px}}.nhsapp-badge-small{position:relative;display:inline-flex;align-items:baseline}.nhsapp-badge-small__indicator{position:relative;width:8px;height:8px;margin-right:8px;border-radius:4px;bottom:calc(.5 * (.7em - 8px));background-color:#d5281b}@media (min-width:40.0625em){.nhsapp-badge-small__indicator{position:relative;width:12px;height:12px;margin-right:12px;border-radius:6px;bottom:calc(.5 * (.7em - 12px))}}.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{position:absolute;left:-16px}@media (min-width:40.0625em){.nhsapp-badge-small--absolute .nhsapp-badge-small__indicator{left:-24px}}.nhsapp-button,.nhsapp-button.nhsuk-button--secondary::before,.nhsapp-button.nhsuk-button--secondary:active{border-radius:8px}.nhsapp-button.nhsuk-button--secondary-solid:not(:focus)::after,.nhsapp-button.nhsuk-button--secondary:not(:focus)::after{border-radius:6px!important}.nhsapp-card{background-color:#fff;border:2px solid #d8dde0;border-radius:8px;position:relative;padding:0}.nhsapp-card{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-card{margin-bottom:40px}}.nhsapp-card__title{font-weight:700;margin-bottom:0}.nhsapp-card__title{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__title{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__title{font-size:13pt;line-height:1.25}}.nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,94,184,.7);flex:none;height:24px;margin-right:-8px;width:24px}@media (min-width:40.0625em){.nhsapp-card .nhsapp-icon--chevron-right{height:28px;margin-right:-10px;width:28px}}.nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#7c2855}.nhsapp-card__container{display:flex;align-items:center;gap:12px;margin:0;padding:16px}@media (min-width:40.0625em){.nhsapp-card__container{gap:16px;margin:0;padding:24px}}.nhsapp-card__content{flex-grow:1}.nhsapp-card__content :last-child{margin-bottom:0}.nhsapp-card__link{font-weight:700;text-decoration:none}.nhsapp-card__link{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__link{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__link{font-size:13pt;line-height:1.25}}.nhsapp-card__link:hover{text-decoration:underline}.nhsapp-card__link::after{bottom:0;content:"";display:block;left:0;position:absolute;right:0;top:0}.nhsapp-card__description{color:#4c6272;margin:0;margin-top:4px}.nhsapp-card__description{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-card__description{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-card__description{font-size:13pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-card__description{margin-top:8px}}.nhsapp-card__below :last-child{margin-bottom:0}.nhsapp-card__footer{border-top:1px solid #d8dde0;margin:0 16px;padding:16px 0}@media (min-width:40.0625em){.nhsapp-card__footer{margin:0 24px;padding:24px 0}}.nhsapp-card__footer :last-child{margin-bottom:0}.nhsapp-cards{list-style:none;padding:0}.nhsapp-cards{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards{margin-bottom:40px}}.nhsapp-cards .nhsapp-card{margin-bottom:16px}@media (min-width:40.0625em){.nhsapp-cards .nhsapp-card{margin-bottom:24px}}.nhsapp-cards .nhsapp-card:last-of-type{margin-bottom:0}.nhsapp-cards--stacked{margin-bottom:32px}@media (min-width:40.0625em){.nhsapp-cards--stacked{margin-bottom:40px}}.nhsapp-cards--stacked .nhsapp-card{border-bottom:0;border-radius:0;border-top:0;margin-bottom:0}.nhsapp-cards--stacked .nhsapp-card .nhsapp-card__container{border-bottom:1px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:first-of-type{border-radius:8px 8px 0 0;border-top:2px solid #d8dde0}.nhsapp-cards--stacked .nhsapp-card:last-of-type{border-bottom:2px solid #d8dde0;border-radius:0 0 8px 8px}.nhsapp-cards--stacked .nhsapp-card:last-of-type .nhsapp-card__container{border-bottom:0}.nhsapp-cards--stacked .nhsapp-card:only-of-type{border-radius:8px;border-top:2px solid #d8dde0;border-bottom:2px solid #d8dde0}.nhsapp-card--secondary,.nhsapp-cards--secondary .nhsapp-card{background:0 0}.nhsapp-cards__heading{padding-top:0}.nhsapp-cards__heading{font-size:19px;font-size:1.1875rem;line-height:1.42105}@media (min-width:40.0625em){.nhsapp-cards__heading{font-size:22px;font-size:1.375rem;line-height:1.36364}}@media print{.nhsapp-cards__heading{font-size:15pt;line-height:1.25}}.nhsapp-cards__heading{margin-bottom:8px}@media (min-width:40.0625em){.nhsapp-cards__heading{margin-bottom:16px}}.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-8px}@media (min-width:40.0625em){.nhsapp-cards__heading+.nhsapp-cards__description{margin-top:-16px}}.nhsapp-cards__description{color:#4c6272;margin-bottom:12px}@media (min-width:40.0625em){.nhsapp-cards__description{margin-bottom:16px}}.nhsapp-card--pale-aqua-green,.nhsapp-cards--pale-aqua-green .nhsapp-card{background:#c9e3e0;border-color:#c9e3e0;color:#1e403d}.nhsapp-card--pale-aqua-green:first-of-type,.nhsapp-card--pale-aqua-green:last-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-aqua-green .nhsapp-card__container,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(30,64,61,.2)}.nhsapp-card--pale-aqua-green .nhsapp-card__link,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover,.nhsapp-card--pale-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#1e403d}.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus,.nhsapp-card--pale-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(30,64,61,.7)}.nhsapp-card--pale-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#1e403d}.nhsapp-card--dark-aqua-green,.nhsapp-cards--dark-aqua-green .nhsapp-card{background:#1e403d;border-color:#1e403d;color:#fff}.nhsapp-card--dark-aqua-green:first-of-type,.nhsapp-card--dark-aqua-green:last-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-aqua-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-aqua-green .nhsapp-card__container,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-aqua-green .nhsapp-card__link,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover,.nhsapp-card--dark-aqua-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-aqua-green .nhsapp-card__link:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus,.nhsapp-card--dark-aqua-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-aqua-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-aqua-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-aqua-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-blue,.nhsapp-cards--pale-blue .nhsapp-card{background:#ccdff1;border-color:#ccdff1;color:#00386e}.nhsapp-card--pale-blue:first-of-type,.nhsapp-card--pale-blue:last-of-type,.nhsapp-cards--pale-blue .nhsapp-card:first-of-type,.nhsapp-cards--pale-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-blue .nhsapp-card__container,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(0,56,110,.2)}.nhsapp-card--pale-blue .nhsapp-card__link,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:hover,.nhsapp-card--pale-blue .nhsapp-card__link:hover:visited,.nhsapp-card--pale-blue .nhsapp-card__link:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:visited{color:#00386e}.nhsapp-card--pale-blue .nhsapp-card__link:focus,.nhsapp-card--pale-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-blue .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,56,110,.7)}.nhsapp-card--pale-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#00386e}.nhsapp-card--dark-blue,.nhsapp-cards--dark-blue .nhsapp-card{background:#00386e;border-color:#00386e;color:#fff}.nhsapp-card--dark-blue:first-of-type,.nhsapp-card--dark-blue:last-of-type,.nhsapp-cards--dark-blue .nhsapp-card:first-of-type,.nhsapp-cards--dark-blue .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-blue .nhsapp-card__container,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-blue .nhsapp-card__link,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:hover,.nhsapp-card--dark-blue .nhsapp-card__link:hover:visited,.nhsapp-card--dark-blue .nhsapp-card__link:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-blue .nhsapp-card__link:focus,.nhsapp-card--dark-blue .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-blue .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-blue:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-blue .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-green,.nhsapp-cards--pale-green .nhsapp-card{background:#cce5d8;border-color:#cce5d8;color:#004c23}.nhsapp-card--pale-green:first-of-type,.nhsapp-card--pale-green:last-of-type,.nhsapp-cards--pale-green .nhsapp-card:first-of-type,.nhsapp-cards--pale-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-green .nhsapp-card__container,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__container{border-color:rgba(0,76,35,.2)}.nhsapp-card--pale-green .nhsapp-card__link,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:hover,.nhsapp-card--pale-green .nhsapp-card__link:hover:visited,.nhsapp-card--pale-green .nhsapp-card__link:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:visited{color:#004c23}.nhsapp-card--pale-green .nhsapp-card__link:focus,.nhsapp-card--pale-green .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-green .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(0,76,35,.7)}.nhsapp-card--pale-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#004c23}.nhsapp-card--dark-green,.nhsapp-cards--dark-green .nhsapp-card{background:#004c23;border-color:#004c23;color:#fff}.nhsapp-card--dark-green:first-of-type,.nhsapp-card--dark-green:last-of-type,.nhsapp-cards--dark-green .nhsapp-card:first-of-type,.nhsapp-cards--dark-green .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-green .nhsapp-card__container,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-green .nhsapp-card__link,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:hover,.nhsapp-card--dark-green .nhsapp-card__link:hover:visited,.nhsapp-card--dark-green .nhsapp-card__link:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-green .nhsapp-card__link:focus,.nhsapp-card--dark-green .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-green .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-green:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-green .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-orange,.nhsapp-cards--pale-orange .nhsapp-card{background:#fbe8cc;border-color:#fbe8cc;color:#5f3800}.nhsapp-card--pale-orange:first-of-type,.nhsapp-card--pale-orange:last-of-type,.nhsapp-cards--pale-orange .nhsapp-card:first-of-type,.nhsapp-cards--pale-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-orange .nhsapp-card__container,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(95,56,0,.2)}.nhsapp-card--pale-orange .nhsapp-card__link,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:hover,.nhsapp-card--pale-orange .nhsapp-card__link:hover:visited,.nhsapp-card--pale-orange .nhsapp-card__link:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:visited{color:#5f3800}.nhsapp-card--pale-orange .nhsapp-card__link:focus,.nhsapp-card--pale-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-orange .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(95,56,0,.7)}.nhsapp-card--pale-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#5f3800}.nhsapp-card--dark-orange,.nhsapp-cards--dark-orange .nhsapp-card{background:#5f3800;border-color:#5f3800;color:#fff}.nhsapp-card--dark-orange:first-of-type,.nhsapp-card--dark-orange:last-of-type,.nhsapp-cards--dark-orange .nhsapp-card:first-of-type,.nhsapp-cards--dark-orange .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-orange .nhsapp-card__container,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-orange .nhsapp-card__link,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:hover,.nhsapp-card--dark-orange .nhsapp-card__link:hover:visited,.nhsapp-card--dark-orange .nhsapp-card__link:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-orange .nhsapp-card__link:focus,.nhsapp-card--dark-orange .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-orange .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-orange:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-orange .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-pink,.nhsapp-cards--pale-pink .nhsapp-card{background:#efd3e3;border-color:#efd3e3;color:#681645}.nhsapp-card--pale-pink:first-of-type,.nhsapp-card--pale-pink:last-of-type,.nhsapp-cards--pale-pink .nhsapp-card:first-of-type,.nhsapp-cards--pale-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-pink .nhsapp-card__container,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(104,22,69,.2)}.nhsapp-card--pale-pink .nhsapp-card__link,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:hover,.nhsapp-card--pale-pink .nhsapp-card__link:hover:visited,.nhsapp-card--pale-pink .nhsapp-card__link:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:visited{color:#681645}.nhsapp-card--pale-pink .nhsapp-card__link:focus,.nhsapp-card--pale-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-pink .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(104,22,69,.7)}.nhsapp-card--pale-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#681645}.nhsapp-card--dark-pink,.nhsapp-cards--dark-pink .nhsapp-card{background:#681645;border-color:#681645;color:#fff}.nhsapp-card--dark-pink:first-of-type,.nhsapp-card--dark-pink:last-of-type,.nhsapp-cards--dark-pink .nhsapp-card:first-of-type,.nhsapp-cards--dark-pink .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-pink .nhsapp-card__container,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-pink .nhsapp-card__link,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:hover,.nhsapp-card--dark-pink .nhsapp-card__link:hover:visited,.nhsapp-card--dark-pink .nhsapp-card__link:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-pink .nhsapp-card__link:focus,.nhsapp-card--dark-pink .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-pink .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-pink:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-pink .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-purple,.nhsapp-cards--pale-purple .nhsapp-card{background:#ded6e8;border-color:#ded6e8;color:#402463}.nhsapp-card--pale-purple:first-of-type,.nhsapp-card--pale-purple:last-of-type,.nhsapp-cards--pale-purple .nhsapp-card:first-of-type,.nhsapp-cards--pale-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-purple .nhsapp-card__container,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(64,36,99,.2)}.nhsapp-card--pale-purple .nhsapp-card__link,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:hover,.nhsapp-card--pale-purple .nhsapp-card__link:hover:visited,.nhsapp-card--pale-purple .nhsapp-card__link:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:visited{color:#402463}.nhsapp-card--pale-purple .nhsapp-card__link:focus,.nhsapp-card--pale-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-purple .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(64,36,99,.7)}.nhsapp-card--pale-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#402463}.nhsapp-card--dark-purple,.nhsapp-cards--dark-purple .nhsapp-card{background:#402463;border-color:#402463;color:#fff}.nhsapp-card--dark-purple:first-of-type,.nhsapp-card--dark-purple:last-of-type,.nhsapp-cards--dark-purple .nhsapp-card:first-of-type,.nhsapp-cards--dark-purple .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-purple .nhsapp-card__container,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-purple .nhsapp-card__link,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:hover,.nhsapp-card--dark-purple .nhsapp-card__link:hover:visited,.nhsapp-card--dark-purple .nhsapp-card__link:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-purple .nhsapp-card__link:focus,.nhsapp-card--dark-purple .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-purple .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-purple:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-purple .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-red,.nhsapp-cards--pale-red .nhsapp-card{background:#f7d4d1;border-color:#f7d4d1;color:#801810}.nhsapp-card--pale-red:first-of-type,.nhsapp-card--pale-red:last-of-type,.nhsapp-cards--pale-red .nhsapp-card:first-of-type,.nhsapp-cards--pale-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-red .nhsapp-card__container,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__container{border-color:rgba(128,24,16,.2)}.nhsapp-card--pale-red .nhsapp-card__link,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:hover,.nhsapp-card--pale-red .nhsapp-card__link:hover:visited,.nhsapp-card--pale-red .nhsapp-card__link:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:visited{color:#801810}.nhsapp-card--pale-red .nhsapp-card__link:focus,.nhsapp-card--pale-red .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-red .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(128,24,16,.7)}.nhsapp-card--pale-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#801810}.nhsapp-card--dark-red,.nhsapp-cards--dark-red .nhsapp-card{background:#801810;border-color:#801810;color:#fff}.nhsapp-card--dark-red:first-of-type,.nhsapp-card--dark-red:last-of-type,.nhsapp-cards--dark-red .nhsapp-card:first-of-type,.nhsapp-cards--dark-red .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-red .nhsapp-card__container,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-red .nhsapp-card__link,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:hover,.nhsapp-card--dark-red .nhsapp-card__link:hover:visited,.nhsapp-card--dark-red .nhsapp-card__link:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-red .nhsapp-card__link:focus,.nhsapp-card--dark-red .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-red .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-red:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-red .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--pale-yellow,.nhsapp-cards--pale-yellow .nhsapp-card{background:#fff7b1;border-color:#fff7b1;color:#4c4612}.nhsapp-card--pale-yellow:first-of-type,.nhsapp-card--pale-yellow:last-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:first-of-type,.nhsapp-cards--pale-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--pale-yellow .nhsapp-card__container,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(76,70,18,.2)}.nhsapp-card--pale-yellow .nhsapp-card__link,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:hover,.nhsapp-card--pale-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--pale-yellow .nhsapp-card__link:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:visited{color:#4c4612}.nhsapp-card--pale-yellow .nhsapp-card__link:focus,.nhsapp-card--pale-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--pale-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(76,70,18,.7)}.nhsapp-card--pale-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--pale-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#4c4612}.nhsapp-card--dark-yellow,.nhsapp-cards--dark-yellow .nhsapp-card{background:#4c4612;border-color:#4c4612;color:#fff}.nhsapp-card--dark-yellow:first-of-type,.nhsapp-card--dark-yellow:last-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:first-of-type,.nhsapp-cards--dark-yellow .nhsapp-card:last-of-type{border:0}.nhsapp-card--dark-yellow .nhsapp-card__container,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__container{border-color:rgba(255,255,255,.2)}.nhsapp-card--dark-yellow .nhsapp-card__link,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:hover,.nhsapp-card--dark-yellow .nhsapp-card__link:hover:visited,.nhsapp-card--dark-yellow .nhsapp-card__link:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:hover:visited,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:visited{color:#fff}.nhsapp-card--dark-yellow .nhsapp-card__link:focus,.nhsapp-card--dark-yellow .nhsapp-card__link:focus:hover,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-card__link:focus:hover{color:#212b32}.nhsapp-card--dark-yellow .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card .nhsapp-icon--chevron-right{fill:rgba(255,255,255,.7)}.nhsapp-card--dark-yellow:hover .nhsapp-icon--chevron-right,.nhsapp-cards--dark-yellow .nhsapp-card:hover .nhsapp-icon--chevron-right{fill:#fff}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 8px 0 0;display:block;max-width:100%}.nhsapp-card--with-media .nhsapp-card__container{padding:24px}@media (min-width:40.0625em){.nhsapp-card--with-media .nhsapp-card__container{padding:32px}}@media (min-width:48.0625em){.nhsapp-card--with-media{display:flex}.nhsapp-card--with-media .nhsapp-card__media{display:flex;flex:2 0}.nhsapp-card--with-media .nhsapp-card__img{border-radius:8px 0 0 8px;flex:none}.nhsapp-card--with-media .nhsapp-card__container{flex:2 0;gap:32px}}@media (min-width:61.875em){.nhsapp-card--with-media .nhsapp-card__media{flex-grow:2}.nhsapp-card--with-media .nhsapp-card__container{flex-grow:3}}.nhsapp-tag{background-color:#ccdff1;border:1px solid transparent;border-radius:2px;color:#00386e;display:inline-block;padding:3px 9px}.nhsapp-tag{font-weight:400}.nhsapp-tag{font-size:14px;font-size:.875rem;line-height:1.25}@media (min-width:40.0625em){.nhsapp-tag{font-size:16px;font-size:1rem;line-height:1.25}}@media print{.nhsapp-tag{font-size:12pt;line-height:1.25}}@media (min-width:40.0625em){.nhsapp-tag{line-height:1.4285em}}.nhsapp-tag--white{background-color:#fff;border-color:#d8dde0;color:#212b32}.nhsapp-tag--grey{background-color:#d8dde0;color:#212b32}.nhsapp-tag--green{background-color:#cce5d8;color:#004c23}.nhsapp-tag--aqua-green{background-color:#c9e3e0;color:#1e403d}.nhsapp-tag--blue{background-color:#ccdff1;color:#00386e}.nhsapp-tag--purple{background-color:#ded6e8;color:#402463}.nhsapp-tag--pink{background-color:#efd3e3;color:#681645}.nhsapp-tag--red{background-color:#f7d4d1;color:#801810}.nhsapp-tag--orange{background-color:#fbe8cc;color:#5f3800}.nhsapp-tag--yellow{background-color:#fff7b1;color:#4c4612}.nhsapp-timeline{list-style:none;padding:0}.nhsapp-timeline{margin-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline{margin-bottom:32px}}.nhsapp-timeline{padding-top:8px}@media (min-width:40.0625em){.nhsapp-timeline{padding-top:8px}}.nhsapp-timeline__item{display:flex;margin-bottom:0;margin-left:12px;margin-top:-6px;position:relative}.nhsapp-timeline__item{padding-bottom:24px}@media (min-width:40.0625em){.nhsapp-timeline__item{padding-bottom:32px}}.nhsapp-timeline__item:last-child{padding:0}.nhsapp-timeline__item:last-child:before{border:none}.nhsapp-timeline__item:before{border-left:2px solid #aeb7bd;bottom:0;content:"";display:block;left:-2px;position:absolute;top:8px;width:2px}.nhsapp-timeline__item--past:before{border-color:#005eb8}.nhsapp-timeline__badge{flex-shrink:0;z-index:1;height:16px;width:16px;margin-left:-9px;margin-top:4px;margin-right:24px}@media (min-width:40.0625em){.nhsapp-timeline__badge{height:20px;margin-left:-11px;margin-top:3px;width:20px}}.nhsapp-timeline__badge--small{height:12px;width:12px;margin-left:-7px;margin-top:6px;margin-right:26px}@media (min-width:40.0625em){.nhsapp-timeline__badge--small{height:16px;margin-left:-9px;margin-top:5px;width:16px}}.nhsapp-timeline__header{font-weight:400;margin-bottom:0}.nhsapp-timeline__header{font-size:16px;font-size:1rem;line-height:1.5}@media (min-width:40.0625em){.nhsapp-timeline__header{font-size:19px;font-size:1.1875rem;line-height:1.47368}}@media print{.nhsapp-timeline__header{font-size:13pt;line-height:1.25}}.nhsapp-timeline__description{margin-bottom:0;padding-top:0}.nhsapp-timeline__description{font-size:14px;font-size:.875rem;line-height:1.71429}@media (min-width:40.0625em){.nhsapp-timeline__description{font-size:16px;font-size:1rem;line-height:1.5}}@media print{.nhsapp-timeline__description{font-size:12pt;line-height:1.3}}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile{display:table;table-layout:fixed;width:100%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__actions,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key,.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{border-bottom:1px solid #d8dde0;display:table-cell;padding-bottom:8px;padding-right:24px;padding-top:8px}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__row{display:table-row}@media (max-width:40.0525em){.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__key{width:50%}}.nhsapp-summary-list--two-columns-mobile .nhsuk-summary-list__value{width:50%}@media (min-width:40.0625em){.nhsapp-u-hide-from-tablet{display:none!important}}@media (max-width:40.0525em){.nhsapp-u-hide-until-tablet{display:none!important}}.nhsapp-u-truncate-two-lines{margin-right:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis} \ No newline at end of file diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx index 9ab6268a1e..79e8c233b3 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; -import DocumentView from './DocumentView'; +import DocumentView, { DOCUMENT_VIEW_STATE } from './DocumentView'; import usePatient from '../../../../helpers/hooks/usePatient'; import useTitle from '../../../../helpers/hooks/useTitle'; import { @@ -120,6 +120,19 @@ const TestApp = ({ documentReference }: Props): React.JSX.Element => { ); }; +const TestAppViewState = ({ documentReference }: Props): React.JSX.Element => { + const history = createMemoryHistory(); + return ( + + + + ); +}; + const renderComponent = ( documentReference: DocumentReference | null = mockDocumentReference, ): void => { @@ -470,6 +483,7 @@ describe('DocumentView', () => { mockUseConfig.mockReturnValue({ featureFlags: { versionHistoryEnabled: true, + documentCorrectEnabled: true, }, }); @@ -515,4 +529,16 @@ describe('DocumentView', () => { expect(screen.queryByTestId('record-menu-card')).not.toBeInTheDocument(); }); }); + + describe('Document view state', () => { + it('renders version history view when viewState is set to VERSION_HISTORY', () => { + render( + + + , + ); + + expect(screen.getByText('Lloyd George records')).toBeInTheDocument(); + }); + }); }); diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx index 9f9049e88b..4aec0d52c5 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx @@ -1,13 +1,24 @@ -import { routeChildren, routes } from '../../../../types/generic/routes'; +import { BackLink, Button, Card, ChevronLeftIcon } from 'nhsuk-react-components'; +import type { MouseEvent } from 'react'; +import { useEffect } from 'react'; +import { + createSearchParams, + NavigateOptions, + To, + useLocation, + useNavigate, +} from 'react-router-dom'; +import useConfig from '../../../../helpers/hooks/useConfig'; +import usePatient from '../../../../helpers/hooks/usePatient'; +import useRole from '../../../../helpers/hooks/useRole'; import useTitle from '../../../../helpers/hooks/useTitle'; -import { useSessionContext } from '../../../../providers/sessionProvider/SessionProvider'; import { DOCUMENT_TYPE, getConfigForDocTypeGeneric, LGContentKeys, } from '../../../../helpers/utils/documentType'; import { getFormattedDate } from '../../../../helpers/utils/formatDate'; -import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types'; +import { useSessionContext } from '../../../../providers/sessionProvider/SessionProvider'; import { ACTION_LINK_KEY, AddAction, @@ -17,31 +28,34 @@ import { ReassignAction, VersionHistoryAction, } from '../../../../types/blocks/lloydGeorgeActions'; -import { createSearchParams, NavigateOptions, To, useNavigate } from 'react-router-dom'; import { REPOSITORY_ROLE } from '../../../../types/generic/authRole'; -import RecordCard from '../../../generic/recordCard/RecordCard'; -import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary'; -import { Button, Card, ChevronLeftIcon } from 'nhsuk-react-components'; -import BackButton from '../../../generic/backButton/BackButton'; -import usePatient from '../../../../helpers/hooks/usePatient'; -import { useEffect } from 'react'; -import useRole from '../../../../helpers/hooks/useRole'; +import { routeChildren, routes } from '../../../../types/generic/routes'; +import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types'; import LinkButton from '../../../generic/linkButton/LinkButton'; -import useConfig from '../../../../helpers/hooks/useConfig'; +import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary'; +import RecordCard from '../../../generic/recordCard/RecordCard'; import Spinner from '../../../generic/spinner/Spinner'; +export enum DOCUMENT_VIEW_STATE { + DOCUMENT = 'DOCUMENT', + VERSION_HISTORY = 'VERSION_HISTORY', +} + type Props = { documentReference: DocumentReference | null; removeDocument: () => void; + viewState?: DOCUMENT_VIEW_STATE; }; const DocumentView = ({ documentReference, removeDocument, + viewState, }: Readonly): React.JSX.Element => { const [session, setUserSession] = useSessionContext(); const role = useRole(); const navigate = useNavigate(); + const { state: isActiveVersion } = useLocation(); const showMenu = role === REPOSITORY_ROLE.GP_ADMIN && !session.isFullscreen; const patientDetails = usePatient(); const config = useConfig(); @@ -52,6 +66,14 @@ const DocumentView = ({ const pageHeader = 'Lloyd George records'; useTitle({ pageTitle: pageHeader }); + const getPdfObjectUrl = (): string => { + if (documentReference?.contentType !== 'application/pdf') { + return ''; + } + + return documentReference.url ? documentReference.url : 'loading'; + }; + // Handle fullscreen changes from browser events useEffect(() => { const handleFullscreenChange = (): void => { @@ -70,7 +92,7 @@ const DocumentView = ({ return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); }; - }, [session, setUserSession]); + }, [session, setUserSession, documentReference, getPdfObjectUrl]); if (!documentReference) { navigate(routes.PATIENT_DOCUMENTS); @@ -122,7 +144,7 @@ const DocumentView = ({ }; const getLinks = (): Array => { - if (session.isFullscreen) { + if (session.isFullscreen || viewState === DOCUMENT_VIEW_STATE.VERSION_HISTORY) { return []; } @@ -182,14 +204,6 @@ const DocumentView = ({ return links.sort((a, b) => a.index - b.index); }; - const getPdfObjectUrl = (): string => { - if (documentReference.contentType !== 'application/pdf') { - return ''; - } - - return documentReference.url ? documentReference.url : 'loading'; - }; - const enableFullscreen = (): void => { if (document.fullscreenEnabled) { document.documentElement.requestFullscreen?.(); @@ -259,6 +273,11 @@ const DocumentView = ({ }, 0); }; + const handleRestoreVersionClick = (): void => { + // eslint-disable-next-line no-console + console.log('Restore version clicked'); // implemented by PRMP-1411 + }; + const getRecordCard = (): React.JSX.Element => { const heading = documentConfig.content.getValueFormatString('viewDocumentTitle', { version: documentReference.version, @@ -353,11 +372,21 @@ const DocumentView = ({
{!session.isFullscreen && ( <> - + , + ): Promise | void => { + e.preventDefault(); + if (viewState === DOCUMENT_VIEW_STATE.VERSION_HISTORY) { + navigate(-1); + return; + } + navigate(routes.PATIENT_DOCUMENTS); + }} + > + Go back +

{pageHeader}

)} @@ -368,6 +397,15 @@ const DocumentView = ({ + {viewState === DOCUMENT_VIEW_STATE.VERSION_HISTORY && !isActiveVersion && ( + + )} + {session.isFullscreen && showMenu && recordCardLinks()}
diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx index 907c4fe6f0..46a6d98290 100644 --- a/app/src/components/blocks/testPanel/TestPanel.tsx +++ b/app/src/components/blocks/testPanel/TestPanel.tsx @@ -1,8 +1,9 @@ +import 'react-toggle/style.css'; import { isLocal } from '../../../helpers/utils/isLocal'; import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider'; import { REPOSITORY_ROLE } from '../../../types/generic/authRole'; +import TestToggle, { ToggleProps } from './TestToggle'; import { FeatureFlags } from '../../../types/generic/featureFlags'; -import TestToggle, { ToggleProps } from './Toggle'; const TestPanel = (): React.JSX.Element => { const [config, setConfig] = useConfigContext(); diff --git a/app/src/components/blocks/testPanel/TestToggle.tsx b/app/src/components/blocks/testPanel/TestToggle.tsx new file mode 100644 index 0000000000..3d2b9e0e3a --- /dev/null +++ b/app/src/components/blocks/testPanel/TestToggle.tsx @@ -0,0 +1,21 @@ +import Toggle from 'react-toggle'; + +export type ToggleProps = { + id: string; + checked: boolean; + label: string; + onChange: () => void; +}; + +const TestToggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { + return ( +
+ + +
+ ); +}; + +export default TestToggle; diff --git a/app/src/components/blocks/testPanel/Toggle.scss b/app/src/components/blocks/testPanel/Toggle.scss deleted file mode 100644 index 954bea161b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.scss +++ /dev/null @@ -1,71 +0,0 @@ -$toggle-track-color: #adb5bd; -$toggle-checked-color: #007f3b; -$toggle-focus-color: #ffb81c; -$toggle-knob-color: #fff; - -.ndr-toggle-div { - margin-bottom: 8px; -} - -.ndr-toggle-label { - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - margin: 0; -} - -// Hide the native checkbox -.ndr-toggle-input { - position: absolute; - opacity: 0; - width: 0; - height: 0; - - // Checked state - &:checked + .ndr-toggle-track { - background-color: $toggle-checked-color; - - &::after { - transform: translateX(22px); - } - } - - // Focus ring for accessibility - &:focus-visible + .ndr-toggle-track { - outline: 3px solid $toggle-focus-color; - outline-offset: 2px; - } -} - -// The track -.ndr-toggle-track { - position: relative; - display: inline-block; - width: 48px; - height: 26px; - background-color: $toggle-track-color; - border-radius: 13px; - cursor: pointer; - flex-shrink: 0; - transition: background-color 0.2s ease; - - // The knob - &::after { - content: ''; - position: absolute; - top: 3px; - left: 3px; - width: 20px; - height: 20px; - background-color: $toggle-knob-color; - border-radius: 50%; - transition: transform 0.2s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - } -} - -.ndr-toggle-paragraph { - margin: 0; - font-size: 14px; -} diff --git a/app/src/components/blocks/testPanel/Toggle.tsx b/app/src/components/blocks/testPanel/Toggle.tsx deleted file mode 100644 index a71ed3224b..0000000000 --- a/app/src/components/blocks/testPanel/Toggle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import './Toggle.scss'; - -export type ToggleProps = { - id: string; - checked: boolean; - label: string; - onChange: () => void; -}; - -const Toggle = ({ id, checked, onChange, label }: ToggleProps): React.JSX.Element => { - return ( -
- -
- ); -}; - -export default Toggle; diff --git a/app/src/config/lloydGeorgeConfig.json b/app/src/config/lloydGeorgeConfig.json index a122ee934e..4819b3af54 100644 --- a/app/src/config/lloydGeorgeConfig.json +++ b/app/src/config/lloydGeorgeConfig.json @@ -46,6 +46,8 @@ "chosenToRemovePagesSubtitle": "You have chosen to remove these pages from the scanned paper notes:", "versionHistoryLinkLabel": "View version history for these notes", "versionHistoryLinkDescription": "View or restore other versions if, for example, there's a mistake in these notes.", - "searchResultDocumentTypeLabel": "Scanned paper notes V{version}" + "searchResultDocumentTypeLabel": "Scanned paper notes V{version}", + "versionHistoryHeader": "Version history for scanned paper notes", + "versionHistoryTimelineHeader": "Scanned paper notes: version {version}" } } \ No newline at end of file diff --git a/app/src/helpers/requests/getReviews.ts b/app/src/helpers/requests/getReviews.ts index f9b1e9f6d9..88078fda13 100644 --- a/app/src/helpers/requests/getReviews.ts +++ b/app/src/helpers/requests/getReviews.ts @@ -18,6 +18,7 @@ import getDocument from './getDocument'; import { fileExtensionToContentType } from '../utils/fileExtensionToContentType'; import { AuthHeaders } from '../../types/blocks/authHeaders'; import { NHS_NUMBER_UNKNOWN } from '../constants/numbers'; +import { fetchBlob } from '../utils/getPdfObjectUrl'; const getReviews = async ( baseUrl: string, @@ -84,13 +85,6 @@ export type GetReviewDataResult = { aborted: boolean; }; -const fetchBlob = async (url: string): Promise => { - const { data } = await axios.get(url, { - responseType: 'blob', - }); - return data; -}; - export const getReviewData = async ({ baseUrl, baseHeaders, diff --git a/app/src/helpers/test/getMockVersionHistory.ts b/app/src/helpers/test/getMockVersionHistory.ts index 0b873ccdbd..b22355cfcd 100644 --- a/app/src/helpers/test/getMockVersionHistory.ts +++ b/app/src/helpers/test/getMockVersionHistory.ts @@ -53,7 +53,7 @@ export const mockDocumentVersionHistoryResponse: Bundle = { attachment: { contentType: 'application/pdf', - url: 'https://documents.example.com/2a7a270e-aa1d-532e-8648-d5d8e3defb82', + url: '/dev/testFile1.pdf', size: 3072, title: 'document_v3.pdf', creation: '2025-12-15T10:30:00Z', @@ -108,7 +108,7 @@ export const mockDocumentVersionHistoryResponse: Bundle = { attachment: { contentType: 'application/pdf', - url: 'https://documents.example.com/c889dbbf-2e3a-5860-ab90-9421b5e29b86', + url: '/dev/testFile.pdf', size: 2048, title: 'document_v2.pdf', creation: '2025-11-10T14:00:00Z', @@ -163,7 +163,7 @@ export const mockDocumentVersionHistoryResponse: Bundle = { attachment: { contentType: 'application/pdf', - url: 'https://documents.example.com/232865e2-c1b5-58c5-bc1c-9d355907b649', + url: '/dev/testFile3.pdf', size: 1024, title: 'document_v1.pdf', creation: '2025-10-01T09:00:00Z', diff --git a/app/src/helpers/utils/documentType.ts b/app/src/helpers/utils/documentType.ts index 1d9f1d63c1..8822a548a4 100644 --- a/app/src/helpers/utils/documentType.ts +++ b/app/src/helpers/utils/documentType.ts @@ -23,7 +23,9 @@ export type LGContentKeys = | ContentKeys | 'versionHistoryLinkLabel' | 'versionHistoryLinkDescription' - | 'searchResultDocumentTypeLabel'; + | 'searchResultDocumentTypeLabel' + | 'versionHistoryHeader' + | 'versionHistoryTimelineHeader'; /** Content keys available to Electronic Health Record documents. */ export type EhrContentKeys = ContentKeys; /** Content keys available to EHR Attachments documents. */ diff --git a/app/src/helpers/utils/fhirUtil.test.ts b/app/src/helpers/utils/fhirUtil.test.ts index 219c116a2b..70e2bbd625 100644 --- a/app/src/helpers/utils/fhirUtil.test.ts +++ b/app/src/helpers/utils/fhirUtil.test.ts @@ -1,7 +1,13 @@ import { Bundle } from '../../types/fhirR4/bundle'; import bundleHistory1Json from '../../types/fhirR4/bundleHistory1.fhir.json'; import { FhirDocumentReference } from '../../types/fhirR4/documentReference'; -import { getCreatedDate, getCustodianValue, getVersionId } from './fhirUtil'; +import { DOCUMENT_TYPE } from './documentType'; +import { + getCreatedDate, + getCustodianValue, + getDocumentReferenceFromFhir, + getVersionId, +} from './fhirUtil'; const buildDoc = (overrides: Partial = {}): FhirDocumentReference => ({ resourceType: 'DocumentReference', ...overrides }) as FhirDocumentReference; @@ -75,4 +81,186 @@ describe('fhirUtil', () => { expect(getCustodianValue(buildDoc({ custodian: { identifier: {} } }))).toBe(''); }); }); + + describe('getDocumentReferenceFromFhir', () => { + it('maps a fully populated FHIR DocumentReference to DocumentReference', () => { + const fhirDoc = buildDoc({ + id: 'doc-123', + date: '2024-06-15T10:00:00Z', + custodian: { identifier: { value: 'ODS999' } }, + type: { coding: [{ code: DOCUMENT_TYPE.LLOYD_GEORGE }] }, + meta: { versionId: '3' }, + content: [ + { + attachment: { + title: 'patient-record.pdf', + size: 54321, + contentType: 'application/pdf', + url: 'https://example.org/fhir/Binary/abc', + }, + }, + ], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result).toEqual({ + documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE, + id: 'doc-123', + created: '2024-06-15T10:00:00Z', + author: 'ODS999', + fileName: 'patient-record.pdf', + fileSize: 54321, + version: '3', + contentType: 'application/pdf', + url: 'https://example.org/fhir/Binary/abc', + isPdf: true, + virusScannerResult: '', + }); + }); + + it('sets isPdf to false for non-PDF content types', () => { + const fhirDoc = buildDoc({ + id: 'doc-456', + content: [ + { + attachment: { + title: 'image.png', + size: 1024, + contentType: 'image/png', + url: 'https://example.org/fhir/Binary/img', + }, + }, + ], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.isPdf).toBe(false); + expect(result.contentType).toBe('image/png'); + }); + + it('defaults fileName to empty string when attachment title is missing', () => { + const fhirDoc = buildDoc({ + id: 'doc-789', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.fileName).toBe(''); + }); + + it('defaults fileSize to 0 when attachment size is missing', () => { + const fhirDoc = buildDoc({ + id: 'doc-size', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.fileSize).toBe(0); + }); + + it('defaults contentType to empty string when not provided', () => { + const fhirDoc = buildDoc({ + id: 'doc-ct', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.contentType).toBe(''); + expect(result.isPdf).toBe(false); + }); + + it('uses custodian display as fallback for author', () => { + const fhirDoc = buildDoc({ + id: 'doc-display', + custodian: { display: 'GP Surgery' }, + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.author).toBe('GP Surgery'); + }); + + it('defaults author to empty string when custodian is missing', () => { + const fhirDoc = buildDoc({ + id: 'doc-no-custodian', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.author).toBe(''); + }); + + it('defaults created to empty string when date is missing', () => { + const fhirDoc = buildDoc({ + id: 'doc-no-date', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.created).toBe(''); + }); + + it('defaults version to empty string when meta is missing', () => { + const fhirDoc = buildDoc({ + id: 'doc-no-meta', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.version).toBe(''); + }); + + it('handles EHR document type', () => { + const fhirDoc = buildDoc({ + id: 'ehr-doc', + type: { coding: [{ code: DOCUMENT_TYPE.EHR }] }, + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.documentSnomedCodeType).toBe(DOCUMENT_TYPE.EHR); + }); + + it('maps the bundleHistory1Json fixture entry correctly', () => { + const doc = (bundleHistory1Json as unknown as Bundle).entry![0] + .resource; + + const result = getDocumentReferenceFromFhir(doc); + + expect(result).toEqual({ + documentSnomedCodeType: undefined, + id: 'LG-12345', + created: '2024-01-10T09:15:00Z', + author: 'A12345', + fileName: 'Lloyd George Record', + fileSize: 120456, + version: '1', + contentType: 'application/pdf', + url: 'https://example.org/fhir/Binary/abcd', + isPdf: true, + virusScannerResult: '', + }); + }); + + it('always sets virusScannerResult to empty string', () => { + const fhirDoc = buildDoc({ + id: 'doc-virus', + content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], + }); + + const result = getDocumentReferenceFromFhir(fhirDoc); + + expect(result.virusScannerResult).toBe(''); + }); + }); }); diff --git a/app/src/helpers/utils/fhirUtil.ts b/app/src/helpers/utils/fhirUtil.ts index e315f014be..a68d7f233c 100644 --- a/app/src/helpers/utils/fhirUtil.ts +++ b/app/src/helpers/utils/fhirUtil.ts @@ -1,4 +1,6 @@ import { FhirDocumentReference } from '../../types/fhirR4/documentReference'; +import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types'; +import { DOCUMENT_TYPE } from './documentType'; /** * Gets the version ID from a FHIR R4 DocumentReference @@ -24,3 +26,32 @@ export const getAuthorValue = (doc: FhirDocumentReference): string => doc.author?.[0]?.display ?? doc.author?.[0]?.reference ?? ''; + +export const getDocumentReferenceFromFhir = ( + fhirDocRef: FhirDocumentReference, +): DocumentReference => { + const documentSnomedCodeType = fhirDocRef.type?.coding?.[0]?.code! as DOCUMENT_TYPE; + const created = getCreatedDate(fhirDocRef); + const author = getAuthorValue(fhirDocRef); + const fileName = fhirDocRef.content?.[0]?.attachment?.title ?? ''; + const id = fhirDocRef.id!; + const fileSize = fhirDocRef.content?.[0]?.attachment?.size ?? 0; + const version = getVersionId(fhirDocRef); + const contentType = fhirDocRef.content?.[0]?.attachment?.contentType ?? ''; + const isPdf = contentType === 'application/pdf'; + let url = fhirDocRef.content?.[0]?.attachment?.url!; + + return { + documentSnomedCodeType, + id, + created, + author, + fileName, + fileSize, + version, + contentType, + url, + isPdf, + virusScannerResult: '', + }; +}; diff --git a/app/src/helpers/utils/getPdfObjectUrl.ts b/app/src/helpers/utils/getPdfObjectUrl.ts index f270a6e18a..b2c85c7a34 100644 --- a/app/src/helpers/utils/getPdfObjectUrl.ts +++ b/app/src/helpers/utils/getPdfObjectUrl.ts @@ -7,9 +7,7 @@ export const getPdfObjectUrl = async ( setPdfObjectUrl: (value: SetStateAction) => void, setDownloadStage: (value: SetStateAction) => void = (): void => {}, ): Promise => { - const { data } = await axios.get(cloudFrontUrl, { - responseType: 'blob', - }); + const data = await fetchBlob(cloudFrontUrl); const objectUrl = URL.createObjectURL(data); @@ -17,3 +15,10 @@ export const getPdfObjectUrl = async ( setDownloadStage(DOWNLOAD_STAGE.SUCCEEDED); return data.size; }; + +export const fetchBlob = async (url: string): Promise => { + const { data } = await axios.get(url, { + responseType: 'blob', + }); + return data; +}; diff --git a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx index 6b368e6018..8ebcba8b1d 100644 --- a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx +++ b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx @@ -26,7 +26,9 @@ import useConfig from '../../helpers/hooks/useConfig'; import { buildSearchResult } from '../../helpers/test/testBuilders'; import { useSessionContext } from '../../providers/sessionProvider/SessionProvider'; import { REPOSITORY_ROLE } from '../../types/generic/authRole'; -import DocumentView from '../../components/blocks/_patientDocuments/documentView/DocumentView'; +import DocumentView, { + DOCUMENT_VIEW_STATE, +} from '../../components/blocks/_patientDocuments/documentView/DocumentView'; import getDocument, { GetDocumentResponse } from '../../helpers/requests/getDocument'; import { DOCUMENT_TYPE } from '../../helpers/utils/documentType'; import BackButton from '../../components/generic/backButton/BackButton'; @@ -194,16 +196,30 @@ const DocumentSearchResultsPage = (): React.JSX.Element => { /> } /> + + } + /> + } /> diff --git a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx b/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx index fe0e33ec75..0e03908a0b 100644 --- a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx +++ b/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx @@ -1,4 +1,5 @@ import { render, RenderResult, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { JSX } from 'react/jsx-runtime'; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders'; @@ -8,6 +9,8 @@ import { getDocumentVersionHistoryResponse } from '../../helpers/requests/getDoc import { mockDocumentVersionHistoryResponse } from '../../helpers/test/getMockVersionHistory'; import { buildPatientDetails, buildSearchResult } from '../../helpers/test/testBuilders'; import { DOCUMENT_TYPE } from '../../helpers/utils/documentType'; +import { fetchBlob } from '../../helpers/utils/getPdfObjectUrl'; +import { routeChildren, routes } from '../../types/generic/routes'; import DocumentVersionHistoryPage from './DocumentVersionHistoryPage'; const mockNavigate = vi.fn(); @@ -32,11 +35,14 @@ vi.mock('../../helpers/hooks/useBaseAPIUrl'); vi.mock('../../helpers/hooks/useBaseAPIHeaders'); vi.mock('../../helpers/requests/getDocumentVersionHistory'); vi.mock('../../helpers/hooks/useTitle'); +vi.mock('../../helpers/utils/getPdfObjectUrl'); const mockedUsePatient = usePatient as Mock; const mockUseBaseAPIUrl = useBaseAPIUrl as Mock; const mockUseBaseAPIHeaders = useBaseAPIHeaders as Mock; const mockGetDocumentVersionHistoryResponse = getDocumentVersionHistoryResponse as Mock; +const mockFetchBlob = fetchBlob as Mock; +const mockSetDocumentReference = vi.fn(); const mockPatientDetails = buildPatientDetails(); const mockDocumentReference = buildSearchResult({ @@ -45,7 +51,12 @@ const mockDocumentReference = buildSearchResult({ }); const renderPage = (): RenderResult => - render(); + render( + , + ); describe('DocumentVersionHistoryPage', () => { beforeEach(() => { @@ -73,6 +84,33 @@ describe('DocumentVersionHistoryPage', () => { }); }); + describe('navigation', () => { + it('navigates to patient documents page when no location state is present', async () => { + mockUseLocation.mockReturnValue({ state: null }); + + render( + , + ); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS); + }); + }); + + it('navigates to server error page when the API call fails', async () => { + mockGetDocumentVersionHistoryResponse.mockRejectedValue(new Error('API error')); + + renderPage(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); + }); + }); + }); + describe('page structure', () => { it('renders the back button', async () => { renderPage(); @@ -93,6 +131,27 @@ describe('DocumentVersionHistoryPage', () => { ).toBeInTheDocument(); }); }); + + it('renders the correct heading for an EHR document type', async () => { + const ehrDocumentReference = buildSearchResult({ + documentSnomedCodeType: DOCUMENT_TYPE.EHR, + }); + + render( + , + ); + + await waitFor(() => { + expect( + screen.getByRole('heading', { + name: /version history for electronic health record/i, + }), + ).toBeInTheDocument(); + }); + }); }); describe('version history timeline', () => { @@ -186,4 +245,109 @@ describe('DocumentVersionHistoryPage', () => { }); }); }); + + describe('null document reference', () => { + it('navigates to patient documents when documentReference is null', () => { + render( + , + ); + + expect(mockNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS); + }); + }); + + describe('error handling', () => { + it('navigates to server error when the version history API call fails', async () => { + mockGetDocumentVersionHistoryResponse.mockRejectedValue(new Error('API error')); + + renderPage(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); + }); + }); + }); + + describe('handleViewVersion', () => { + it('navigates to version history view when clicking View on the current version', async () => { + const user = userEvent.setup(); + renderPage(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-3')); + + await waitFor(() => { + expect(mockSetDocumentReference).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith( + routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, + { state: true }, + ); + }); + }); + + it('navigates to version history view when clicking View on an older version', async () => { + const user = userEvent.setup(); + renderPage(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect(mockSetDocumentReference).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith( + routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, + { state: undefined }, + ); + }); + }); + + it('fetches blob and sets document reference URL when the document has a URL', async () => { + const mockBlobUrl = 'blob:http://localhost/mock-blob'; + const mockBlob = new Blob(['test'], { type: 'application/pdf' }); + mockFetchBlob.mockResolvedValue(mockBlob); + vi.spyOn(URL, 'createObjectURL').mockReturnValue(mockBlobUrl); + + const user = userEvent.setup(); + renderPage(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-3')); + + await waitFor(() => { + expect(mockFetchBlob).toHaveBeenCalledWith('/dev/testFile1.pdf'); + expect(mockSetDocumentReference).toHaveBeenCalledWith( + expect.objectContaining({ url: mockBlobUrl }), + ); + }); + }); + + it('navigates to server error when handleViewVersion throws', async () => { + mockFetchBlob.mockRejectedValue(new Error('Blob fetch failed')); + + const user = userEvent.setup(); + renderPage(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-3')); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); + }); + }); + }); }); diff --git a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx b/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx index 3b20998d80..a2ca05bc92 100644 --- a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx +++ b/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx @@ -10,20 +10,34 @@ import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl'; import usePatient from '../../helpers/hooks/usePatient'; import useTitle from '../../helpers/hooks/useTitle'; import { getDocumentVersionHistoryResponse } from '../../helpers/requests/getDocumentVersionHistory'; -import { getDocumentTypeLabel } from '../../helpers/utils/documentType'; -import { getAuthorValue, getCreatedDate, getVersionId } from '../../helpers/utils/fhirUtil'; +import { + getConfigForDocTypeGeneric, + getDocumentTypeLabel, + LGContentKeys, +} from '../../helpers/utils/documentType'; +import { + getAuthorValue, + getCreatedDate, + getDocumentReferenceFromFhir, + getVersionId, +} from '../../helpers/utils/fhirUtil'; import { getFormatDateWithAtTime } from '../../helpers/utils/formatDate'; +import { fetchBlob } from '../../helpers/utils/getPdfObjectUrl'; import { Bundle } from '../../types/fhirR4/bundle'; import { FhirDocumentReference } from '../../types/fhirR4/documentReference'; -import { routes } from '../../types/generic/routes'; +import { routeChildren, routes } from '../../types/generic/routes'; import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types'; +import { AxiosError } from 'axios'; +import { errorToParams } from '../../helpers/utils/errorToParams'; type DocumentVersionHistoryPageProps = { documentReference: DocumentReference | null; + setDocumentReference: (docRef: DocumentReference) => void; }; const DocumentVersionHistoryPage = ({ documentReference, + setDocumentReference, }: DocumentVersionHistoryPageProps): React.JSX.Element => { const navigate = useNavigate(); const baseUrl = useBaseAPIUrl(); @@ -31,27 +45,30 @@ const DocumentVersionHistoryPage = ({ const versionHistoryRef = useRef(false); const patientDetails = usePatient(); const nhsNumber = patientDetails?.nhsNumber ?? ''; + const [loading, setLoading] = useState(true); + const [versionHistory, setVersionHistory] = useState | null>( + null, + ); const docTypeLabel = documentReference ? getDocumentTypeLabel(documentReference.documentSnomedCodeType) : ''; - const pageHeader = `Version history for ${docTypeLabel.toLowerCase()}`; + const docConfig = documentReference + ? getConfigForDocTypeGeneric(documentReference.documentSnomedCodeType) + : null; + const pageHeader = + docConfig?.content.getValue('versionHistoryHeader') || + `Version history for ${docTypeLabel}`; useTitle({ pageTitle: pageHeader }); - const [loading, setLoading] = useState(true); - const [versionHistory, setVersionHistory] = useState | null>( - null, - ); - useEffect(() => { if (!documentReference) { navigate(routes.PATIENT_DOCUMENTS); return; } - - const fetchVersionHistory = async (): Promise => { - if (!versionHistoryRef.current) { - versionHistoryRef.current = true; + if (!versionHistoryRef.current) { + versionHistoryRef.current = true; + const fetchVersionHistory = async (): Promise => { try { const response = await getDocumentVersionHistoryResponse({ nhsNumber, @@ -60,26 +77,62 @@ const DocumentVersionHistoryPage = ({ documentReferenceId: documentReference.id, }); setVersionHistory(response); - } catch { - navigate(routes.PATIENT_DOCUMENTS); - } finally { setLoading(false); - } - } - }; - void fetchVersionHistory(); - }, [documentReference, nhsNumber, baseUrl, baseHeaders, navigate]); + } catch (e) { + const error = e as AxiosError; + setLoading(false); + if (error.response?.status === 403) { + navigate(routes.SESSION_EXPIRED); + } else if (error.response?.status && error.response?.status >= 500) { + navigate(routes.SERVER_ERROR + errorToParams(error)); + } - if (loading) { - return ; - } + navigate(routes.SERVER_ERROR); + } + }; + void fetchVersionHistory(); + } + }, [documentReference, nhsNumber, baseUrl]); if (!documentReference) { navigate(routes.PATIENT_DOCUMENTS); return <>; } + const handleViewVersion = async ( + e: React.MouseEvent, + doc: FhirDocumentReference, + isActiveVersion?: boolean, + ): Promise => { + e.preventDefault(); + setLoading(true); + try { + const documentRef = getDocumentReferenceFromFhir(doc); + setDocumentReference({ ...documentRef, url: '' }); + + navigate(routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, { state: isActiveVersion }); + + if (documentRef.url) { + const blobUrl = await fetchBlob(documentRef.url); + setDocumentReference({ ...documentRef, url: URL.createObjectURL(blobUrl) }); + } + } catch (e) { + const error = e as AxiosError; + setLoading(false); + if (error.response?.status === 403) { + navigate(routes.SESSION_EXPIRED); + } else if (error.response?.status && error.response?.status >= 500) { + navigate(routes.SERVER_ERROR + errorToParams(error)); + } + + navigate(routes.SERVER_ERROR); + } + }; + const renderVersionHistoryTimeline = (): React.JSX.Element => { + if (loading) { + return ; + } if (!versionHistory?.entry || versionHistory.entry.length === 0) { return

No version history available for this document.

; } @@ -91,14 +144,27 @@ const DocumentVersionHistoryPage = ({ return ( {sortedEntries.map((entry, index) => { - const isCurrentVersion = index === 0; - const status = isCurrentVersion + const maxVersion = versionHistory.entry?.sort( + (a, b) => + Number(getVersionId(b.resource)) - Number(getVersionId(a.resource)), + )[0]; + + if (!maxVersion) { + return <>; + } + + const isActiveVersion = entry.resource.id === maxVersion.resource.id; + const status = isActiveVersion ? TimelineStatus.Active : TimelineStatus.Inactive; const isLastItem = index === versionHistory.entry!.length - 1; const doc = entry.resource; const version = getVersionId(doc); - const heading = `${docTypeLabel}: version ${version}`; + const heading = + docConfig?.content.getValueFormatString( + 'versionHistoryTimelineHeader', + { version }, + ) || `${docTypeLabel}: version ${version}`; return ( - {isCurrentVersion && ( + {isActiveVersion && ( This is the current version shown in this patient's record @@ -125,11 +191,15 @@ const DocumentVersionHistoryPage = ({ dateUploaded={getFormatDateWithAtTime(getCreatedDate(doc))} /> - {isCurrentVersion ? ( + {isActiveVersion ? ( , + ): Promise => handleViewVersion(e, doc, isActiveVersion)} > View @@ -138,6 +208,9 @@ const DocumentVersionHistoryPage = ({ diff --git a/app/src/types/blocks/lloydGeorgeActions.ts b/app/src/types/blocks/lloydGeorgeActions.ts index 08637e2c27..3a79221db9 100644 --- a/app/src/types/blocks/lloydGeorgeActions.ts +++ b/app/src/types/blocks/lloydGeorgeActions.ts @@ -84,7 +84,7 @@ export const VersionHistoryAction = ( index: 4, label: label, key: ACTION_LINK_KEY.HISTORY, - type: RECORD_ACTION.UPDATE, // This could be a different type if needed + type: RECORD_ACTION.UPDATE, unauthorised: [], onClick, showIfRecordInStorage: true, diff --git a/app/src/types/fhirR4/bundle.ts b/app/src/types/fhirR4/bundle.ts index 87be6e27b1..d993e30ae2 100644 --- a/app/src/types/fhirR4/bundle.ts +++ b/app/src/types/fhirR4/bundle.ts @@ -132,11 +132,7 @@ export interface BundleEntry { /** A resource in the bundle */ resource: T; /** Search related information */ - search?: BundleEntrySearch; /** Additional execution information (transaction/batch/history) */ - request?: BundleEntryRequest; - /** Results of execution (transaction/batch/history) */ - response?: BundleEntryResponse; } // ─── Bundle Resource ───────────────────────────────────────────────────────── diff --git a/app/src/types/generic/fhir.ts b/app/src/types/generic/fhir.ts deleted file mode 100644 index 69484b1924..0000000000 --- a/app/src/types/generic/fhir.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type Bundle = { - resourceType: string; - type: string; - total: number; - entry: Array>; -}; - -export type BundleEntry = { - fullUrl?: string; - resource: T; -}; From b8b6a0a1e7ffbf068e3d5cd7889e8ac78b2e1bf3 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Fri, 20 Mar 2026 10:58:06 +0000 Subject: [PATCH 07/14] minor fix to tests --- app/src/helpers/utils/fhirUtil.test.ts | 6 +++--- app/src/types/generic/routes.ts | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/helpers/utils/fhirUtil.test.ts b/app/src/helpers/utils/fhirUtil.test.ts index 70e2bbd625..dea67984ec 100644 --- a/app/src/helpers/utils/fhirUtil.test.ts +++ b/app/src/helpers/utils/fhirUtil.test.ts @@ -87,7 +87,7 @@ describe('fhirUtil', () => { const fhirDoc = buildDoc({ id: 'doc-123', date: '2024-06-15T10:00:00Z', - custodian: { identifier: { value: 'ODS999' } }, + author: [{ identifier: { value: 'ODS999' } }], type: { coding: [{ code: DOCUMENT_TYPE.LLOYD_GEORGE }] }, meta: { versionId: '3' }, content: [ @@ -174,10 +174,10 @@ describe('fhirUtil', () => { expect(result.isPdf).toBe(false); }); - it('uses custodian display as fallback for author', () => { + it('uses author value as fallback for author', () => { const fhirDoc = buildDoc({ id: 'doc-display', - custodian: { display: 'GP Surgery' }, + author: [{ display: 'GP Surgery' }], content: [{ attachment: { url: 'https://example.org/fhir/Binary/x' } }], }); diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts index 4a07d5cf18..826b733920 100644 --- a/app/src/types/generic/routes.ts +++ b/app/src/types/generic/routes.ts @@ -77,9 +77,6 @@ export enum routeChildren { DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', - DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', - DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', - DOCUMENT_VIEW = '/patient/documents/view', DOCUMENT_DELETE = '/patient/documents/delete', DOCUMENT_DELETE_CONFIRMATION = '/patient/documents/delete/confirmation', From bf5b17b6c7827b572234acd20bd29bef6f6d78ee Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Fri, 20 Mar 2026 11:31:33 +0000 Subject: [PATCH 08/14] minor fix --- app/src/types/fhirR4/bundle.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/types/fhirR4/bundle.ts b/app/src/types/fhirR4/bundle.ts index d993e30ae2..5413cb3bda 100644 --- a/app/src/types/fhirR4/bundle.ts +++ b/app/src/types/fhirR4/bundle.ts @@ -131,8 +131,6 @@ export interface BundleEntry { fullUrl?: string; /** A resource in the bundle */ resource: T; - /** Search related information */ - /** Additional execution information (transaction/batch/history) */ } // ─── Bundle Resource ───────────────────────────────────────────────────────── From 294f8febc7ef932ff6277a23242de6b8e55e1de6 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Mon, 23 Mar 2026 11:10:30 +0000 Subject: [PATCH 09/14] code comment change --- .../blocks/_patientDocuments/documentView/DocumentView.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx index 79e8c233b3..7acdf7eae6 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx @@ -483,7 +483,6 @@ describe('DocumentView', () => { mockUseConfig.mockReturnValue({ featureFlags: { versionHistoryEnabled: true, - documentCorrectEnabled: true, }, }); From 0573201cd93df6d5589f7273007df78b30780185 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Tue, 24 Mar 2026 09:14:47 +0000 Subject: [PATCH 10/14] bring it fixs from 1411 --- ...cumentVersionRestoreHistoryStage.test.tsx} | 202 ++++++++---------- .../DocumentVersionRestoreHistoryStage.tsx} | 170 ++++++++++----- .../documentView/DocumentView.tsx | 10 +- app/src/helpers/requests/getDocument.ts | 6 +- .../requests/getDocumentSearchResults.ts | 4 +- app/src/helpers/utils/getPdfObjectUrl.ts | 6 + .../DocumentSearchResultsPage.tsx | 20 -- .../DocumentVersionRestorePage.test.tsx | 168 +++++++++++++++ .../DocumentVersionRestorePage.tsx | 57 +++++ app/src/router/AppRouter.tsx | 11 + app/src/types/generic/routes.ts | 5 +- 11 files changed, 462 insertions(+), 197 deletions(-) rename app/src/{pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx => components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx} (61%) rename app/src/{pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx => components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.tsx} (60%) create mode 100644 app/src/pages/documentVersionPage/DocumentVersionRestorePage.test.tsx create mode 100644 app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx diff --git a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx similarity index 61% rename from app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx rename to app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx index 0e03908a0b..15c20a2d7f 100644 --- a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.test.tsx +++ b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx @@ -2,16 +2,18 @@ import { render, RenderResult, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { JSX } from 'react/jsx-runtime'; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; -import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders'; -import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl'; -import usePatient from '../../helpers/hooks/usePatient'; -import { getDocumentVersionHistoryResponse } from '../../helpers/requests/getDocumentVersionHistory'; -import { mockDocumentVersionHistoryResponse } from '../../helpers/test/getMockVersionHistory'; -import { buildPatientDetails, buildSearchResult } from '../../helpers/test/testBuilders'; -import { DOCUMENT_TYPE } from '../../helpers/utils/documentType'; -import { fetchBlob } from '../../helpers/utils/getPdfObjectUrl'; -import { routeChildren, routes } from '../../types/generic/routes'; -import DocumentVersionHistoryPage from './DocumentVersionHistoryPage'; +import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders'; +import useBaseAPIUrl from '../../../../helpers/hooks/useBaseAPIUrl'; +import usePatient from '../../../../helpers/hooks/usePatient'; +import getDocument from '../../../../helpers/requests/getDocument'; +import getDocumentSearchResults from '../../../../helpers/requests/getDocumentSearchResults'; +import { getDocumentVersionHistoryResponse } from '../../../../helpers/requests/getDocumentVersionHistory'; +import { mockDocumentVersionHistoryResponse } from '../../../../helpers/test/getMockVersionHistory'; +import { buildPatientDetails, buildSearchResult } from '../../../../helpers/test/testBuilders'; +import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType'; +import { routeChildren, routes } from '../../../../types/generic/routes'; +import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types'; +import DocumentVersionRestoreHistoryStage from './DocumentVersionRestoreHistoryStage'; const mockNavigate = vi.fn(); const mockUseLocation = vi.fn(); @@ -30,18 +32,21 @@ vi.mock('react-router-dom', async () => { }; }); -vi.mock('../../helpers/hooks/usePatient'); -vi.mock('../../helpers/hooks/useBaseAPIUrl'); -vi.mock('../../helpers/hooks/useBaseAPIHeaders'); -vi.mock('../../helpers/requests/getDocumentVersionHistory'); -vi.mock('../../helpers/hooks/useTitle'); -vi.mock('../../helpers/utils/getPdfObjectUrl'); +vi.mock('../../../../helpers/hooks/usePatient'); +vi.mock('../../../../helpers/hooks/useBaseAPIUrl'); +vi.mock('../../../../helpers/hooks/useBaseAPIHeaders'); +vi.mock('../../../../helpers/requests/getDocumentVersionHistory'); +vi.mock('../../../../helpers/requests/getDocument'); +vi.mock('../../../../helpers/requests/getDocumentSearchResults'); +vi.mock('../../../../helpers/hooks/useTitle'); +vi.mock('../../../../helpers/utils/getPdfObjectUrl'); const mockedUsePatient = usePatient as Mock; const mockUseBaseAPIUrl = useBaseAPIUrl as Mock; const mockUseBaseAPIHeaders = useBaseAPIHeaders as Mock; const mockGetDocumentVersionHistoryResponse = getDocumentVersionHistoryResponse as Mock; -const mockFetchBlob = fetchBlob as Mock; +const mockGetDocument = getDocument as Mock; +const mockGetDocumentSearchResults = getDocumentSearchResults as Mock; const mockSetDocumentReference = vi.fn(); const mockPatientDetails = buildPatientDetails(); @@ -50,15 +55,19 @@ const mockDocumentReference = buildSearchResult({ id: 'doc-ref-123', }); -const renderPage = (): RenderResult => - render( - , - ); +describe('DocumentVersionRestoreHistoryStage', () => { + const renderApp = ( + documentReference: DocumentReference | null = mockDocumentReference, + ): RenderResult => + render( + , + ); -describe('DocumentVersionHistoryPage', () => { beforeEach(() => { import.meta.env.VITE_ENVIRONMENT = 'vitest'; mockedUsePatient.mockReturnValue(mockPatientDetails); @@ -68,6 +77,13 @@ describe('DocumentVersionHistoryPage', () => { state: { documentReference: mockDocumentReference }, }); mockGetDocumentVersionHistoryResponse.mockResolvedValue(mockDocumentVersionHistoryResponse); + mockGetDocument.mockResolvedValue({ + url: 'http://test-url.com/doc.pdf', + contentType: 'application/pdf', + }); + mockGetDocumentSearchResults.mockResolvedValue([ + buildSearchResult({ id: 'latest-doc-ref-id' }), + ]); }); afterEach(() => { @@ -78,32 +94,38 @@ describe('DocumentVersionHistoryPage', () => { it('renders a spinner while the version history is loading', () => { mockGetDocumentVersionHistoryResponse.mockReturnValue(new Promise(() => {})); - renderPage(); + renderApp(); expect(screen.getByText('Loading version history')).toBeInTheDocument(); }); }); describe('navigation', () => { - it('navigates to patient documents page when no location state is present', async () => { - mockUseLocation.mockReturnValue({ state: null }); + it('fetches version history using the latest document reference id from search', async () => { + const viewedVersionDocumentReference = buildSearchResult({ + documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE, + id: 'doc-ref-viewed-version', + }); + + mockGetDocumentSearchResults.mockResolvedValue([ + buildSearchResult({ id: 'doc-ref-from-search' }), + ]); - render( - , - ); + renderApp(viewedVersionDocumentReference); await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS); + expect(mockGetDocumentVersionHistoryResponse).toHaveBeenCalledWith( + expect.objectContaining({ + documentReferenceId: 'doc-ref-from-search', + }), + ); }); }); it('navigates to server error page when the API call fails', async () => { mockGetDocumentVersionHistoryResponse.mockRejectedValue(new Error('API error')); - renderPage(); + renderApp(); await waitFor(() => { expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); @@ -113,7 +135,7 @@ describe('DocumentVersionHistoryPage', () => { describe('page structure', () => { it('renders the back button', async () => { - renderPage(); + renderApp(); await waitFor(() => { expect(screen.getByTestId('go-back-button')).toBeInTheDocument(); @@ -121,7 +143,7 @@ describe('DocumentVersionHistoryPage', () => { }); it('renders the page heading with the correct document type label', async () => { - renderPage(); + renderApp(); await waitFor(() => { expect( @@ -137,12 +159,7 @@ describe('DocumentVersionHistoryPage', () => { documentSnomedCodeType: DOCUMENT_TYPE.EHR, }); - render( - , - ); + renderApp(ehrDocumentReference); await waitFor(() => { expect( @@ -163,7 +180,7 @@ describe('DocumentVersionHistoryPage', () => { entry: [], }); - renderPage(); + renderApp(); await waitFor(() => { expect( @@ -179,7 +196,7 @@ describe('DocumentVersionHistoryPage', () => { total: 0, }); - renderPage(); + renderApp(); await waitFor(() => { expect( @@ -189,7 +206,7 @@ describe('DocumentVersionHistoryPage', () => { }); it('renders all version history entries with correct headings', async () => { - renderPage(); + renderApp(); await waitFor(() => { expect(screen.getByText('Scanned paper notes: version 3')).toBeInTheDocument(); @@ -199,7 +216,7 @@ describe('DocumentVersionHistoryPage', () => { }); it('shows "this is the current version" only for the first (most recent) entry', async () => { - renderPage(); + renderApp(); await waitFor(() => { const currentVersionMessages = screen.getAllByText( @@ -210,7 +227,7 @@ describe('DocumentVersionHistoryPage', () => { }); it('renders a "View" link (not a button) for the current version', async () => { - renderPage(); + renderApp(); await waitFor(() => { const viewCurrentLink = screen.getByTestId('view-version-3'); @@ -219,51 +236,32 @@ describe('DocumentVersionHistoryPage', () => { }); }); - it('renders a "View" button and a "Restore version" link for each older version', async () => { - renderPage(); + it('renders a "View" button for each older version without a restore link', async () => { + renderApp(); await waitFor(() => { const viewVersion2 = screen.getByTestId('view-version-2'); expect(viewVersion2.tagName.toLowerCase()).toBe('button'); - const restoreVersion2 = screen.getByTestId('restore-version-2'); - expect(restoreVersion2).toHaveTextContent('Restore version'); - const viewVersion1 = screen.getByTestId('view-version-1'); expect(viewVersion1.tagName.toLowerCase()).toBe('button'); - - const restoreVersion1 = screen.getByTestId('restore-version-1'); - expect(restoreVersion1).toHaveTextContent('Restore version'); }); }); - it('does not render a "Restore version" link for the current version', async () => { - renderPage(); + it('does not render any "Restore version" links', async () => { + renderApp(); await waitFor(() => { - expect(screen.queryByTestId('restore-version-3')).not.toBeInTheDocument(); + expect(screen.queryByText('Restore version')).not.toBeInTheDocument(); }); }); }); - describe('null document reference', () => { - it('navigates to patient documents when documentReference is null', () => { - render( - , - ); - - expect(mockNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS); - }); - }); - describe('error handling', () => { it('navigates to server error when the version history API call fails', async () => { mockGetDocumentVersionHistoryResponse.mockRejectedValue(new Error('API error')); - renderPage(); + renderApp(); await waitFor(() => { expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); @@ -274,7 +272,7 @@ describe('DocumentVersionHistoryPage', () => { describe('handleViewVersion', () => { it('navigates to version history view when clicking View on the current version', async () => { const user = userEvent.setup(); - renderPage(); + renderApp(); await waitFor(() => { expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); @@ -286,14 +284,18 @@ describe('DocumentVersionHistoryPage', () => { expect(mockSetDocumentReference).toHaveBeenCalled(); expect(mockNavigate).toHaveBeenCalledWith( routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, - { state: true }, + expect.objectContaining({ + state: expect.objectContaining({ + isActiveVersion: true, + }), + }), ); }); }); it('navigates to version history view when clicking View on an older version', async () => { const user = userEvent.setup(); - renderPage(); + renderApp(); await waitFor(() => { expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); @@ -305,49 +307,13 @@ describe('DocumentVersionHistoryPage', () => { expect(mockSetDocumentReference).toHaveBeenCalled(); expect(mockNavigate).toHaveBeenCalledWith( routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, - { state: undefined }, - ); - }); - }); - - it('fetches blob and sets document reference URL when the document has a URL', async () => { - const mockBlobUrl = 'blob:http://localhost/mock-blob'; - const mockBlob = new Blob(['test'], { type: 'application/pdf' }); - mockFetchBlob.mockResolvedValue(mockBlob); - vi.spyOn(URL, 'createObjectURL').mockReturnValue(mockBlobUrl); - - const user = userEvent.setup(); - renderPage(); - - await waitFor(() => { - expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('view-version-3')); - - await waitFor(() => { - expect(mockFetchBlob).toHaveBeenCalledWith('/dev/testFile1.pdf'); - expect(mockSetDocumentReference).toHaveBeenCalledWith( - expect.objectContaining({ url: mockBlobUrl }), + expect.objectContaining({ + state: expect.objectContaining({ + isActiveVersion: false, + }), + }), ); }); }); - - it('navigates to server error when handleViewVersion throws', async () => { - mockFetchBlob.mockRejectedValue(new Error('Blob fetch failed')); - - const user = userEvent.setup(); - renderPage(); - - await waitFor(() => { - expect(screen.getByTestId('view-version-3')).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('view-version-3')); - - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); - }); - }); }); }); diff --git a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.tsx similarity index 60% rename from app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx rename to app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.tsx index a2ca05bc92..e22115c466 100644 --- a/app/src/pages/documentVersionHistoryPage/DocumentVersionHistoryPage.tsx +++ b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.tsx @@ -1,44 +1,52 @@ +import { AxiosError } from 'axios'; import { Button } from 'nhsuk-react-components'; import { useEffect, useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import BackButton from '../../components/generic/backButton/BackButton'; -import { CreatedByText } from '../../components/generic/createdBy/createdBy'; -import Spinner from '../../components/generic/spinner/Spinner'; -import Timeline, { TimelineStatus } from '../../components/generic/timeline/Timeline'; -import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders'; -import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl'; -import usePatient from '../../helpers/hooks/usePatient'; -import useTitle from '../../helpers/hooks/useTitle'; -import { getDocumentVersionHistoryResponse } from '../../helpers/requests/getDocumentVersionHistory'; +import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders'; +import useBaseAPIUrl from '../../../../helpers/hooks/useBaseAPIUrl'; +import usePatient from '../../../../helpers/hooks/usePatient'; +import useTitle from '../../../../helpers/hooks/useTitle'; +import getDocument, { GetDocumentResponse } from '../../../../helpers/requests/getDocument'; +import getDocumentSearchResults from '../../../../helpers/requests/getDocumentSearchResults'; +import { getDocumentVersionHistoryResponse } from '../../../../helpers/requests/getDocumentVersionHistory'; import { + DOCUMENT_TYPE, getConfigForDocTypeGeneric, getDocumentTypeLabel, LGContentKeys, -} from '../../helpers/utils/documentType'; +} from '../../../../helpers/utils/documentType'; +import { errorToParams } from '../../../../helpers/utils/errorToParams'; import { getAuthorValue, getCreatedDate, getDocumentReferenceFromFhir, getVersionId, -} from '../../helpers/utils/fhirUtil'; -import { getFormatDateWithAtTime } from '../../helpers/utils/formatDate'; -import { fetchBlob } from '../../helpers/utils/getPdfObjectUrl'; -import { Bundle } from '../../types/fhirR4/bundle'; -import { FhirDocumentReference } from '../../types/fhirR4/documentReference'; -import { routeChildren, routes } from '../../types/generic/routes'; -import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types'; -import { AxiosError } from 'axios'; -import { errorToParams } from '../../helpers/utils/errorToParams'; +} from '../../../../helpers/utils/fhirUtil'; +import { getFormatDateWithAtTime } from '../../../../helpers/utils/formatDate'; +import { getObjectUrl } from '../../../../helpers/utils/getPdfObjectUrl'; +import { isLocal } from '../../../../helpers/utils/isLocal'; +import { Bundle } from '../../../../types/fhirR4/bundle'; +import { FhirDocumentReference } from '../../../../types/fhirR4/documentReference'; +import { routeChildren, routes } from '../../../../types/generic/routes'; +import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types'; +import BackButton from '../../../generic/backButton/BackButton'; +import { CreatedByText } from '../../../generic/createdBy/createdBy'; +import Spinner from '../../../generic/spinner/Spinner'; +import Timeline, { TimelineStatus } from '../../../generic/timeline/Timeline'; -type DocumentVersionHistoryPageProps = { +type DocumentVersionRestoreHistoryPageProps = { documentReference: DocumentReference | null; + setDocumentReferenceToRestore: (docRef: DocumentReference) => void; setDocumentReference: (docRef: DocumentReference) => void; + setLatestVersion: (version: string) => void; }; -const DocumentVersionHistoryPage = ({ +const DocumentVersionRestoreHistoryStage = ({ documentReference, + setDocumentReferenceToRestore, setDocumentReference, -}: DocumentVersionHistoryPageProps): React.JSX.Element => { + setLatestVersion, +}: Readonly): React.JSX.Element => { const navigate = useNavigate(); const baseUrl = useBaseAPIUrl(); const baseHeaders = useBaseAPIHeaders(); @@ -62,20 +70,36 @@ const DocumentVersionHistoryPage = ({ useTitle({ pageTitle: pageHeader }); useEffect(() => { - if (!documentReference) { - navigate(routes.PATIENT_DOCUMENTS); - return; - } if (!versionHistoryRef.current) { versionHistoryRef.current = true; const fetchVersionHistory = async (): Promise => { try { + const searchResults = await getDocumentSearchResults({ + nhsNumber, + baseUrl, + baseHeaders, + docType: + documentReference?.documentSnomedCodeType ?? DOCUMENT_TYPE.LLOYD_GEORGE, + limit: 1, + }); + + setDocumentReference(searchResults[0]); + const latestDocRefId = searchResults[0]?.id; + setLatestVersion(searchResults[0]?.version ?? ''); + + if (!latestDocRefId) { + setLoading(false); + navigate(routes.SERVER_ERROR); + return; + } + const response = await getDocumentVersionHistoryResponse({ nhsNumber, baseUrl, baseHeaders, - documentReferenceId: documentReference.id, + documentReferenceId: latestDocRefId, }); + setVersionHistory(response); setLoading(false); } catch (e) { @@ -92,12 +116,57 @@ const DocumentVersionHistoryPage = ({ }; void fetchVersionHistory(); } - }, [documentReference, nhsNumber, baseUrl]); + }, []); - if (!documentReference) { - navigate(routes.PATIENT_DOCUMENTS); - return <>; - } + const loadDocument = async ( + documentId: string, + version?: string, + baseRef?: DocumentReference, + ): Promise => { + try { + const documentResponse = await getDocument({ + nhsNumber: patientDetails!.nhsNumber, + baseUrl, + baseHeaders, + documentId, + version, + }); + + const docRef = await handleViewDocSuccess(documentResponse, baseRef); + return docRef; + } catch (e) { + if (isLocal) { + const docRef = await handleViewDocSuccess( + { + url: '/dev/testFile.pdf', + contentType: 'application/pdf', + }, + baseRef, + ); + return docRef; + } + const error = e as AxiosError; + if (error.response?.status === 403) { + navigate(routes.SESSION_EXPIRED); + } else if (error.response?.status === 404) { + await handleViewDocSuccess({ url: '', contentType: '' }, baseRef); + } else { + navigate(routes.SERVER_ERROR + errorToParams(error)); + } + } + }; + + const handleViewDocSuccess = async ( + documentResponse: GetDocumentResponse, + baseRef?: DocumentReference, + ): Promise => { + const ref = baseRef ?? documentReference!; + return { + ...ref, + url: documentResponse.url ? await getObjectUrl(documentResponse.url) : null, + isPdf: documentResponse.contentType === 'application/pdf', + }; + }; const handleViewVersion = async ( e: React.MouseEvent, @@ -108,14 +177,18 @@ const DocumentVersionHistoryPage = ({ setLoading(true); try { const documentRef = getDocumentReferenceFromFhir(doc); - setDocumentReference({ ...documentRef, url: '' }); + let documentRefId = documentRef.id; + if (documentRef.id.includes('~')) { + documentRefId = documentRef.id.split('~')[1]; + } - navigate(routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, { state: isActiveVersion }); + const docRef = await loadDocument(documentRefId, documentRef.version, documentRef); - if (documentRef.url) { - const blobUrl = await fetchBlob(documentRef.url); - setDocumentReference({ ...documentRef, url: URL.createObjectURL(blobUrl) }); - } + setDocumentReferenceToRestore(docRef!); + + navigate(routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, { + state: { isActiveVersion }, + }); } catch (e) { const error = e as AxiosError; setLoading(false); @@ -129,10 +202,10 @@ const DocumentVersionHistoryPage = ({ } }; + if (loading) { + return ; + } const renderVersionHistoryTimeline = (): React.JSX.Element => { - if (loading) { - return ; - } if (!versionHistory?.entry || versionHistory.entry.length === 0) { return

No version history available for this document.

; } @@ -157,7 +230,7 @@ const DocumentVersionHistoryPage = ({ const status = isActiveVersion ? TimelineStatus.Active : TimelineStatus.Inactive; - const isLastItem = index === versionHistory.entry!.length - 1; + const isLastItem = index === sortedEntries.length - 1; const doc = entry.resource; const version = getVersionId(doc); const heading = @@ -199,7 +272,7 @@ const DocumentVersionHistoryPage = ({ className=" nhsuk-link nhsuk-link--no-visited-state" onClick={( e: React.MouseEvent, - ): Promise => handleViewVersion(e, doc, isActiveVersion)} + ): Promise => handleViewVersion(e, doc, true)} > View @@ -210,17 +283,10 @@ const DocumentVersionHistoryPage = ({ className="nhsuk-u-margin-right-3 nhsuk-link--no-visited-state" onClick={( e: React.MouseEvent, - ): Promise => handleViewVersion(e, doc)} + ): Promise => handleViewVersion(e, doc, false)} > View - - Restore version - )}
@@ -241,4 +307,4 @@ const DocumentVersionHistoryPage = ({ ); }; -export default DocumentVersionHistoryPage; +export default DocumentVersionRestoreHistoryStage; diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx index 4aec0d52c5..293241b0fd 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx @@ -5,7 +5,6 @@ import { createSearchParams, NavigateOptions, To, - useLocation, useNavigate, } from 'react-router-dom'; import useConfig from '../../../../helpers/hooks/useConfig'; @@ -43,19 +42,20 @@ export enum DOCUMENT_VIEW_STATE { type Props = { documentReference: DocumentReference | null; - removeDocument: () => void; + removeDocument?: () => void; viewState?: DOCUMENT_VIEW_STATE; + isActiveVersion?: boolean; }; const DocumentView = ({ documentReference, removeDocument, viewState, + isActiveVersion, }: Readonly): React.JSX.Element => { const [session, setUserSession] = useSessionContext(); const role = useRole(); const navigate = useNavigate(); - const { state: isActiveVersion } = useLocation(); const showMenu = role === REPOSITORY_ROLE.GP_ADMIN && !session.isFullscreen; const patientDetails = usePatient(); const config = useConfig(); @@ -140,7 +140,9 @@ const DocumentView = ({ const removeClicked = (): void => { disableFullscreen(); - removeDocument(); + if (removeDocument) { + removeDocument(); + } }; const getLinks = (): Array => { diff --git a/app/src/helpers/requests/getDocument.ts b/app/src/helpers/requests/getDocument.ts index 71e3b6d012..bd4a97a98f 100644 --- a/app/src/helpers/requests/getDocument.ts +++ b/app/src/helpers/requests/getDocument.ts @@ -8,6 +8,7 @@ export type GetDocumentArgs = { baseUrl: string; baseHeaders: AuthHeaders; documentId: string; + version?: string; }; export type GetDocumentResponse = { @@ -20,6 +21,7 @@ const getDocument = async ({ baseUrl, baseHeaders, documentId, + version, }: GetDocumentArgs): Promise => { if (isLocal) { return { @@ -28,7 +30,9 @@ const getDocument = async ({ }; } - const gatewayUrl = baseUrl + endpoints.DOCUMENT_REFERENCE + `/${documentId}`; + const gatewayUrl = version + ? `${baseUrl}${endpoints.DOCUMENT_REFERENCE}/${documentId}/_history/${version}` + : `${baseUrl}${endpoints.DOCUMENT_REFERENCE}/${documentId}`; try { const { data } = await axios.get(gatewayUrl, { diff --git a/app/src/helpers/requests/getDocumentSearchResults.ts b/app/src/helpers/requests/getDocumentSearchResults.ts index fd29758ea6..fc93a44174 100644 --- a/app/src/helpers/requests/getDocumentSearchResults.ts +++ b/app/src/helpers/requests/getDocumentSearchResults.ts @@ -11,6 +11,7 @@ export type DocumentSearchResultsArgs = { baseUrl: string; baseHeaders: AuthHeaders; docType?: DOCUMENT_TYPE; + limit?: number; }; export type GetDocumentSearchResultsResponse = { @@ -22,6 +23,7 @@ const getDocumentSearchResults = async ({ baseUrl, baseHeaders, docType, + limit, }: DocumentSearchResultsArgs): Promise> => { const gatewayUrl = baseUrl + endpoints.DOCUMENT_SEARCH; @@ -33,7 +35,7 @@ const getDocumentSearchResults = async ({ params: { patientId: nhsNumber?.replaceAll(/\s/g, ''), // replace whitespace docType: docType === DOCUMENT_TYPE.ALL ? undefined : docType, - limit: 9999, + limit: limit ?? 9999, }, }); return data.references; diff --git a/app/src/helpers/utils/getPdfObjectUrl.ts b/app/src/helpers/utils/getPdfObjectUrl.ts index b2c85c7a34..90ac7663bc 100644 --- a/app/src/helpers/utils/getPdfObjectUrl.ts +++ b/app/src/helpers/utils/getPdfObjectUrl.ts @@ -22,3 +22,9 @@ export const fetchBlob = async (url: string): Promise => { }); return data; }; + +export const getObjectUrl = async (cloudFrontUrl: string): Promise => { + const data = await fetchBlob(cloudFrontUrl); + + return URL.createObjectURL(data); +}; diff --git a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx index 8ebcba8b1d..339e5e792b 100644 --- a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx +++ b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx @@ -36,7 +36,6 @@ import ProgressBar from '../../components/generic/progressBar/ProgressBar'; import DeleteSubmitStage from '../../components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage'; import { Button, WarningCallout } from 'nhsuk-react-components'; import getReviews from '../../helpers/requests/getReviews'; -import DocumentVersionHistoryPage from '../documentVersionHistoryPage/DocumentVersionHistoryPage'; const DocumentSearchResultsPage = (): React.JSX.Element => { const patientDetails = usePatient(); @@ -196,25 +195,6 @@ const DocumentSearchResultsPage = (): React.JSX.Element => { /> } /> - - } - /> - - } - /> { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useNavigate: (): Mock => mockNavigate, + }; +}); + +vi.mock( + '../../components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage', + () => ({ + default: (props: { + documentReference: DocumentReference | null; + setDocumentReferenceToRestore: (docRef: DocumentReference) => void; + setDocumentReference: (docRef: DocumentReference) => void; + setLatestVersion: (version: string) => void; + }): React.JSX.Element => ( +
+ {props.documentReference && ( + {props.documentReference.id} + )} +
+ ), + }), +); + +vi.mock('../../components/blocks/_patientDocuments/documentView/DocumentView', () => ({ + default: (props: { + documentReference: DocumentReference | null; + viewState?: string; + isActiveVersion?: boolean; + }): React.JSX.Element => ( +
+ {props.viewState && {props.viewState}} + {props.isActiveVersion !== undefined && ( + {String(props.isActiveVersion)} + )} + {props.documentReference && ( + {props.documentReference.id} + )} +
+ ), + DOCUMENT_VIEW_STATE: { + DOCUMENT: 'DOCUMENT', + VERSION_HISTORY: 'VERSION_HISTORY', + }, +})); + +vi.mock('../../helpers/hooks/useConfig'); + +const mockedUseConfig = useConfig as Mock; + +describe('DocumentVersionRestorePage', () => { + beforeEach(() => { + import.meta.env.VITE_ENVIRONMENT = 'vitest'; + mockedUseConfig.mockReturnValue({ + featureFlags: { + versionHistoryEnabled: true, + }, + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('Feature flag guard', () => { + it('navigates to home when versionHistoryEnabled is false', async () => { + mockedUseConfig.mockReturnValue({ + featureFlags: { + versionHistoryEnabled: false, + }, + }); + + renderPage(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.HOME); + }); + }); + + it('renders empty fragment when versionHistoryEnabled is false', () => { + mockedUseConfig.mockReturnValue({ + featureFlags: { + versionHistoryEnabled: false, + }, + }); + + const { container } = renderPage(); + + expect(container.innerHTML).toBe(''); + }); + + it('does not navigate to home when versionHistoryEnabled is true', () => { + renderPage(); + + expect(mockNavigate).not.toHaveBeenCalledWith(routes.HOME); + }); + + it('navigates to home when featureFlags is undefined', async () => { + mockedUseConfig.mockReturnValue({ + featureFlags: undefined, + }); + + renderPage(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.HOME); + }); + }); + }); + + describe('Rendering', () => { + it('renders the history stage at the index route', () => { + renderPage(); + + expect(screen.getByTestId('history-stage')).toBeInTheDocument(); + }); + + it('renders the document view at the view route', () => { + renderPage('/patient/documents/version-history/view'); + + expect(screen.getByTestId('document-view')).toBeInTheDocument(); + }); + + it('passes VERSION_HISTORY view state to DocumentView', () => { + renderPage('/patient/documents/version-history/view'); + + expect(screen.getByTestId('view-state')).toHaveTextContent('VERSION_HISTORY'); + }); + }); + + describe('State management', () => { + it('passes null documentReference to history stage initially', () => { + renderPage(); + + expect(screen.queryByTestId('history-doc-ref')).not.toBeInTheDocument(); + }); + }); + + const renderPage = ( + initialPath: string = '/patient/documents/version-history', + ): RenderResult => { + const router = createMemoryRouter( + [ + { + path: '/patient/documents/version-history/*', + element: , + }, + ], + { + initialEntries: [initialPath], + }, + ); + + return render(); + }; +}); diff --git a/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx new file mode 100644 index 0000000000..c4d7c7575a --- /dev/null +++ b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import DocumentVersionRestoreHistoryStage from '../../components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage'; +import DocumentView, { + DOCUMENT_VIEW_STATE, +} from '../../components/blocks/_patientDocuments/documentView/DocumentView'; +import useConfig from '../../helpers/hooks/useConfig'; +import { getLastURLPath } from '../../helpers/utils/urlManipulations'; +import { routeChildren, routes } from '../../types/generic/routes'; +import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types'; + +const DocumentVersionRestorePage = (): React.JSX.Element => { + const [documentReferenceToRestore, setDocumentReferenceToRestore] = + useState(null); + const [documentReference, setDocumentReference] = useState(null); + const [latestVersion, setLatestVersion] = useState(''); + const config = useConfig(); + const navigate = useNavigate(); + + useEffect(() => { + if (!config.featureFlags?.versionHistoryEnabled) { + navigate(routes.HOME); + } + }, [config.featureFlags, navigate]); + + if (!config.featureFlags?.versionHistoryEnabled) { + return <>; + } + + return ( + + + } + /> + + } + /> + + ); +}; + +export default DocumentVersionRestorePage; diff --git a/app/src/router/AppRouter.tsx b/app/src/router/AppRouter.tsx index 7e7a12e2f6..047e1ee4df 100644 --- a/app/src/router/AppRouter.tsx +++ b/app/src/router/AppRouter.tsx @@ -33,6 +33,7 @@ import CookiePolicyPage from '../pages/cookiePolicyPage/CookiePolicyPage'; import DocumentCorrectPage from '../pages/documentCorrectPage/DocumentCorrectPage'; import { AdminPage } from '../pages/adminPage/AdminPage'; import UserPatientRestrictionsPage from '../pages/userPatientRestrictionsPage/UserPatientRestrictionsPage'; +import DocumentVersionRestorePage from '../pages/documentVersionPage/DocumentVersionRestorePage'; const { START, @@ -69,6 +70,8 @@ const { COOKIES_POLICY_WILDCARD, DOCUMENT_REASSIGN_PAGES, DOCUMENT_REASSIGN_PAGES_WILDCARD, + DOCUMENT_VERSION_HISTORY, + DOCUMENT_VERSION_HISTORY_WILDCARD, USER_PATIENT_RESTRICTIONS, } = routes; @@ -414,6 +417,14 @@ export const routeMap: Routes = { page: , type: ROUTE_TYPE.PATIENT, }, + [DOCUMENT_VERSION_HISTORY]: { + page: , + type: ROUTE_TYPE.PATIENT, + }, + [DOCUMENT_VERSION_HISTORY_WILDCARD]: { + page: , + type: ROUTE_TYPE.PATIENT, + }, [USER_PATIENT_RESTRICTIONS]: { page: , type: ROUTE_TYPE.PRIVATE, diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts index 826b733920..1fa5bb0564 100644 --- a/app/src/types/generic/routes.ts +++ b/app/src/types/generic/routes.ts @@ -30,6 +30,9 @@ export enum routes { DOCUMENT_REASSIGN_PAGES = '/patient/document-reassign-pages', DOCUMENT_REASSIGN_PAGES_WILDCARD = '/patient/document-reassign-pages/*', + DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', + DOCUMENT_VERSION_HISTORY_WILDCARD = '/patient/documents/version-history/*', + MOCK_LOGIN = 'Auth/MockLogin', ADMIN_ROUTE = '/admin', @@ -74,7 +77,7 @@ export enum routeChildren { DOCUMENT_REASSIGN_UPLOADING = '/patient/document-reassign-pages/uploading', DOCUMENT_REASSIGN_COMPLETE = '/patient/document-reassign-pages/complete', - DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view', + DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history/view', DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history', DOCUMENT_VIEW = '/patient/documents/view', From 4025b074263431cca458d6e55ed0b08c99e9d17e Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Tue, 24 Mar 2026 09:32:03 +0000 Subject: [PATCH 11/14] minor fix --- .../blocks/_patientDocuments/documentView/DocumentView.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx index 293241b0fd..0219dc9ce2 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx @@ -1,12 +1,7 @@ import { BackLink, Button, Card, ChevronLeftIcon } from 'nhsuk-react-components'; import type { MouseEvent } from 'react'; import { useEffect } from 'react'; -import { - createSearchParams, - NavigateOptions, - To, - useNavigate, -} from 'react-router-dom'; +import { createSearchParams, NavigateOptions, To, useNavigate } from 'react-router-dom'; import useConfig from '../../../../helpers/hooks/useConfig'; import usePatient from '../../../../helpers/hooks/usePatient'; import useRole from '../../../../helpers/hooks/useRole'; From 9d08909821b0964a4a037f90ef3e668186eef4f4 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Tue, 24 Mar 2026 09:45:36 +0000 Subject: [PATCH 12/14] added test coverage --- app/src/helpers/requests/getDocument.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/helpers/requests/getDocument.test.ts b/app/src/helpers/requests/getDocument.test.ts index b33fe2d0cc..6f278967d2 100644 --- a/app/src/helpers/requests/getDocument.test.ts +++ b/app/src/helpers/requests/getDocument.test.ts @@ -46,6 +46,26 @@ describe('getDocument', () => { expect(result).toEqual(mockResponse); }); + it('should use the version history route when version is provided', async () => { + mockedAxios.get.mockResolvedValueOnce({ data: mockResponse }); + + const result = await getDocument({ + ...mockArgs, + version: '7', + }); + + expect(mockedAxios.get).toHaveBeenCalledWith( + `${mockArgs.baseUrl}${endpoints.DOCUMENT_REFERENCE}/${mockArgs.documentId}/_history/7`, + { + headers: mockArgs.baseHeaders, + params: { + patientId: mockArgs.nhsNumber, + }, + }, + ); + expect(result).toEqual(mockResponse); + }); + it('should throw AxiosError when request fails', async () => { const mockError = new Error('Network Error'); mockedAxios.get.mockRejectedValueOnce(mockError); From b3e9e5bd6576bb8372a533a3f7222f78e4466565 Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Tue, 24 Mar 2026 09:56:27 +0000 Subject: [PATCH 13/14] added test coverage --- app/src/helpers/utils/getPdfObjectUrl.test.ts | 474 ++++++++++-------- 1 file changed, 264 insertions(+), 210 deletions(-) diff --git a/app/src/helpers/utils/getPdfObjectUrl.test.ts b/app/src/helpers/utils/getPdfObjectUrl.test.ts index 0ceadd0eba..c74549a4f5 100644 --- a/app/src/helpers/utils/getPdfObjectUrl.test.ts +++ b/app/src/helpers/utils/getPdfObjectUrl.test.ts @@ -1,288 +1,342 @@ import { describe, expect, it, vi, Mock, Mocked, beforeEach, afterEach } from 'vitest'; import axios from 'axios'; -import { getPdfObjectUrl } from './getPdfObjectUrl'; +import { getPdfObjectUrl, getObjectUrl } from './getPdfObjectUrl'; import { DOWNLOAD_STAGE } from '../../types/generic/downloadStage'; vi.mock('axios'); const mockedAxios = axios as Mocked; -describe('getPdfObjectUrl', () => { - const mockSetPdfObjectUrl = vi.fn(); - const mockSetDownloadStage = vi.fn(); - const testCloudFrontUrl = 'https://cloudfront.example.com/test-file.pdf'; +describe('getPdfObjectUrl.ts', () => { + describe('getPdfObjectUrl', () => { + const mockSetPdfObjectUrl = vi.fn(); + const mockSetDownloadStage = vi.fn(); + const testCloudFrontUrl = 'https://cloudfront.example.com/test-file.pdf'; - // Mock URL.createObjectURL - const mockObjectUrl = 'blob:http://localhost:3000/test-blob-id'; - const originalCreateObjectURL = URL.createObjectURL; + // Mock URL.createObjectURL + const mockObjectUrl = 'blob:http://localhost:3000/test-blob-id'; + const originalCreateObjectURL = URL.createObjectURL; - beforeEach(() => { - vi.clearAllMocks(); - URL.createObjectURL = vi.fn((): string => mockObjectUrl); - }); + beforeEach(() => { + vi.clearAllMocks(); + URL.createObjectURL = vi.fn((): string => mockObjectUrl); + }); - afterEach(() => { - URL.createObjectURL = originalCreateObjectURL; - }); + afterEach(() => { + URL.createObjectURL = originalCreateObjectURL; + }); - describe('Successful PDF download', () => { - it('fetches PDF from cloudFrontUrl with correct config', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + describe('Successful PDF download', () => { + it('fetches PDF from cloudFrontUrl with correct config', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(mockedAxios.get).toHaveBeenCalledTimes(1); - expect(mockedAxios.get).toHaveBeenCalledWith(testCloudFrontUrl, { - responseType: 'blob', + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith(testCloudFrontUrl, { + responseType: 'blob', + }); }); - }); - - it('creates blob URL from response data', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + it('creates blob URL from response data', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - expect(URL.createObjectURL).toHaveBeenCalledTimes(1); - expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); - }); - - it('calls setPdfObjectUrl with created object URL', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + expect(URL.createObjectURL).toHaveBeenCalledTimes(1); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + }); - expect(mockSetPdfObjectUrl).toHaveBeenCalledTimes(1); - expect(mockSetPdfObjectUrl).toHaveBeenCalledWith(mockObjectUrl); - }); + it('calls setPdfObjectUrl with created object URL', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - it('sets download stage to SUCCEEDED', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + expect(mockSetPdfObjectUrl).toHaveBeenCalledTimes(1); + expect(mockSetPdfObjectUrl).toHaveBeenCalledWith(mockObjectUrl); + }); - expect(mockSetDownloadStage).toHaveBeenCalledTimes(1); - expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); - }); + it('sets download stage to SUCCEEDED', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - it('completes successfully with all callbacks', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).resolves.not.toThrow(); + expect(mockSetDownloadStage).toHaveBeenCalledTimes(1); + expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); + }); - expect(mockSetPdfObjectUrl).toHaveBeenCalled(); - expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); - }); - }); + it('completes successfully with all callbacks', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - describe('Error handling', () => { - it('throws error when axios request fails', async () => { - const mockError = new Error('Network error'); - mockedAxios.get.mockRejectedValue(mockError); + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).resolves.not.toThrow(); - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).rejects.toThrow('Network error'); + expect(mockSetPdfObjectUrl).toHaveBeenCalled(); + expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); + }); }); - it('does not call setPdfObjectUrl or setDownloadStage on error', async () => { - const mockError = new Error('Network error'); - mockedAxios.get.mockRejectedValue(mockError); + describe('Error handling', () => { + it('throws error when axios request fails', async () => { + const mockError = new Error('Network error'); + mockedAxios.get.mockRejectedValue(mockError); - try { - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - } catch (error) { - // Expected error - } + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).rejects.toThrow('Network error'); + }); - expect(mockSetPdfObjectUrl).not.toHaveBeenCalled(); - expect(mockSetDownloadStage).not.toHaveBeenCalled(); - }); + it('does not call setPdfObjectUrl or setDownloadStage on error', async () => { + const mockError = new Error('Network error'); + mockedAxios.get.mockRejectedValue(mockError); + + try { + await getPdfObjectUrl( + testCloudFrontUrl, + mockSetPdfObjectUrl, + mockSetDownloadStage, + ); + } catch (error) { + // Expected error + } + + expect(mockSetPdfObjectUrl).not.toHaveBeenCalled(); + expect(mockSetDownloadStage).not.toHaveBeenCalled(); + }); - it('handles 403 forbidden error', async () => { - const mockError = { - response: { - status: 403, - statusText: 'Forbidden', - }, - message: 'Request failed with status code 403', - }; - mockedAxios.get.mockRejectedValue(mockError); - - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).rejects.toMatchObject({ - message: 'Request failed with status code 403', + it('handles 403 forbidden error', async () => { + const mockError = { + response: { + status: 403, + statusText: 'Forbidden', + }, + message: 'Request failed with status code 403', + }; + mockedAxios.get.mockRejectedValue(mockError); + + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).rejects.toMatchObject({ + message: 'Request failed with status code 403', + }); }); - }); - it('handles 404 not found error', async () => { - const mockError = { - response: { - status: 404, - statusText: 'Not Found', - }, - message: 'Request failed with status code 404', - }; - mockedAxios.get.mockRejectedValue(mockError); - - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).rejects.toMatchObject({ - message: 'Request failed with status code 404', + it('handles 404 not found error', async () => { + const mockError = { + response: { + status: 404, + statusText: 'Not Found', + }, + message: 'Request failed with status code 404', + }; + mockedAxios.get.mockRejectedValue(mockError); + + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).rejects.toMatchObject({ + message: 'Request failed with status code 404', + }); }); - }); - it('handles 500 server error', async () => { - const mockError = { - response: { - status: 500, - statusText: 'Internal Server Error', - }, - message: 'Request failed with status code 500', - }; - mockedAxios.get.mockRejectedValue(mockError); - - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).rejects.toMatchObject({ - message: 'Request failed with status code 500', + it('handles 500 server error', async () => { + const mockError = { + response: { + status: 500, + statusText: 'Internal Server Error', + }, + message: 'Request failed with status code 500', + }; + mockedAxios.get.mockRejectedValue(mockError); + + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).rejects.toMatchObject({ + message: 'Request failed with status code 500', + }); }); - }); - it('handles timeout error', async () => { - const mockError = { - code: 'ECONNABORTED', - message: 'timeout of 30000ms exceeded', - }; - mockedAxios.get.mockRejectedValue(mockError); - - await expect( - getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), - ).rejects.toMatchObject({ - message: 'timeout of 30000ms exceeded', + it('handles timeout error', async () => { + const mockError = { + code: 'ECONNABORTED', + message: 'timeout of 30000ms exceeded', + }; + mockedAxios.get.mockRejectedValue(mockError); + + await expect( + getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage), + ).rejects.toMatchObject({ + message: 'timeout of 30000ms exceeded', + }); }); }); - }); - describe('Different blob types', () => { - it('handles large PDF blobs', async () => { - // Create a larger blob to simulate real PDFs - const largeData = new Uint8Array(1024 * 1024); // 1MB - const mockBlob = new Blob([largeData], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + describe('Different blob types', () => { + it('handles large PDF blobs', async () => { + // Create a larger blob to simulate real PDFs + const largeData = new Uint8Array(1024 * 1024); // 1MB + const mockBlob = new Blob([largeData], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); - expect(mockSetPdfObjectUrl).toHaveBeenCalledWith(mockObjectUrl); - }); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(mockSetPdfObjectUrl).toHaveBeenCalledWith(mockObjectUrl); + }); - it('handles empty blob', async () => { - const mockBlob = new Blob([], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + it('handles empty blob', async () => { + const mockBlob = new Blob([], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); - expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); - }); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); + }); - it('handles blob without explicit type', async () => { - const mockBlob = new Blob(['test data']); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + it('handles blob without explicit type', async () => { + const mockBlob = new Blob(['test data']); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(testCloudFrontUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); - expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.SUCCEEDED); + }); }); - }); - describe('Different URLs', () => { - it('handles different CloudFront URLs', async () => { - const differentUrl = 'https://cdn.example.com/documents/patient123.pdf'; - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + describe('Different URLs', () => { + it('handles different CloudFront URLs', async () => { + const differentUrl = 'https://cdn.example.com/documents/patient123.pdf'; + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(differentUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(differentUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(mockedAxios.get).toHaveBeenCalledWith(differentUrl, { - responseType: 'blob', + expect(mockedAxios.get).toHaveBeenCalledWith(differentUrl, { + responseType: 'blob', + }); }); - }); - it('handles URLs with query parameters', async () => { - //TODO Review tests - const urlWithParams = 'https://cloudfront.example.com/file.pdf?token=abc&expires=123'; - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + it('handles URLs with query parameters', async () => { + //TODO Review tests + const urlWithParams = + 'https://cloudfront.example.com/file.pdf?token=abc&expires=123'; + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(urlWithParams, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(urlWithParams, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(mockedAxios.get).toHaveBeenCalledWith(urlWithParams, { - responseType: 'blob', + expect(mockedAxios.get).toHaveBeenCalledWith(urlWithParams, { + responseType: 'blob', + }); }); - }); - it('handles URLs with special characters', async () => { - const specialUrl = 'https://cloudfront.example.com/files/patient%20record%20(1).pdf'; - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); + it('handles URLs with special characters', async () => { + const specialUrl = + 'https://cloudfront.example.com/files/patient%20record%20(1).pdf'; + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); - await getPdfObjectUrl(specialUrl, mockSetPdfObjectUrl, mockSetDownloadStage); + await getPdfObjectUrl(specialUrl, mockSetPdfObjectUrl, mockSetDownloadStage); - expect(mockedAxios.get).toHaveBeenCalledWith(specialUrl, { - responseType: 'blob', + expect(mockedAxios.get).toHaveBeenCalledWith(specialUrl, { + responseType: 'blob', + }); }); }); - }); - - describe('Callback execution order', () => { - it('calls setPdfObjectUrl before setDownloadStage', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); - mockedAxios.get.mockResolvedValue({ data: mockBlob }); - const callOrder: string[] = []; - const trackingSetPdfObjectUrl = vi.fn((): void => { - callOrder.push('setPdfObjectUrl'); + describe('Callback execution order', () => { + it('calls setPdfObjectUrl before setDownloadStage', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); + + const callOrder: string[] = []; + const trackingSetPdfObjectUrl = vi.fn((): void => { + callOrder.push('setPdfObjectUrl'); + }); + const trackingSetDownloadStage = vi.fn((): void => { + callOrder.push('setDownloadStage'); + }); + + await getPdfObjectUrl( + testCloudFrontUrl, + trackingSetPdfObjectUrl, + trackingSetDownloadStage, + ); + + expect(callOrder).toEqual(['setPdfObjectUrl', 'setDownloadStage']); }); - const trackingSetDownloadStage = vi.fn((): void => { - callOrder.push('setDownloadStage'); + + it('ensures blob URL is created before calling setPdfObjectUrl', async () => { + const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); + + const callOrder: string[] = []; + (URL.createObjectURL as Mock).mockImplementation((): string => { + callOrder.push('createObjectURL'); + return mockObjectUrl; + }); + const trackingSetPdfObjectUrl = vi.fn((): void => { + callOrder.push('setPdfObjectUrl'); + }); + + await getPdfObjectUrl( + testCloudFrontUrl, + trackingSetPdfObjectUrl, + mockSetDownloadStage, + ); + + expect(callOrder[0]).toBe('createObjectURL'); + expect(callOrder[1]).toBe('setPdfObjectUrl'); }); + }); + }); + + describe('getObjectUrl', () => { + const testCloudFrontUrl = 'https://cloudfront.example.com/test-file.pdf'; + const mockObjectUrl = 'blob:http://localhost:3000/test-blob-id'; + const originalCreateObjectURL = URL.createObjectURL; - await getPdfObjectUrl( - testCloudFrontUrl, - trackingSetPdfObjectUrl, - trackingSetDownloadStage, - ); + beforeEach(() => { + vi.clearAllMocks(); + URL.createObjectURL = vi.fn((): string => mockObjectUrl); + }); - expect(callOrder).toEqual(['setPdfObjectUrl', 'setDownloadStage']); + afterEach(() => { + URL.createObjectURL = originalCreateObjectURL; }); - it('ensures blob URL is created before calling setPdfObjectUrl', async () => { - const mockBlob = new Blob(['test pdf data'], { type: 'application/pdf' }); + it('fetches blob from the provided URL', async () => { + const mockBlob = new Blob(['test data'], { type: 'application/pdf' }); mockedAxios.get.mockResolvedValue({ data: mockBlob }); - const callOrder: string[] = []; - (URL.createObjectURL as Mock).mockImplementation((): string => { - callOrder.push('createObjectURL'); - return mockObjectUrl; - }); - const trackingSetPdfObjectUrl = vi.fn((): void => { - callOrder.push('setPdfObjectUrl'); + await getObjectUrl(testCloudFrontUrl); + + expect(mockedAxios.get).toHaveBeenCalledWith(testCloudFrontUrl, { + responseType: 'blob', }); + }); + + it('returns a blob object URL', async () => { + const mockBlob = new Blob(['test data'], { type: 'application/pdf' }); + mockedAxios.get.mockResolvedValue({ data: mockBlob }); + + const result = await getObjectUrl(testCloudFrontUrl); + + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(result).toBe(mockObjectUrl); + }); - await getPdfObjectUrl(testCloudFrontUrl, trackingSetPdfObjectUrl, mockSetDownloadStage); + it('throws when the fetch fails', async () => { + mockedAxios.get.mockRejectedValue(new Error('Network error')); - expect(callOrder[0]).toBe('createObjectURL'); - expect(callOrder[1]).toBe('setPdfObjectUrl'); + await expect(getObjectUrl(testCloudFrontUrl)).rejects.toThrow('Network error'); }); }); }); From a6c8dadb59d55bb041ab105c8c94faf0b5bb90af Mon Sep 17 00:00:00 2001 From: Lillie Dae Date: Tue, 24 Mar 2026 10:18:43 +0000 Subject: [PATCH 14/14] add test coverage --- ...ocumentVersionRestoreHistoryStage.test.tsx | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx index 15c20a2d7f..d26d5b829a 100644 --- a/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx +++ b/app/src/components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage.test.tsx @@ -11,6 +11,8 @@ import { getDocumentVersionHistoryResponse } from '../../../../helpers/requests/ import { mockDocumentVersionHistoryResponse } from '../../../../helpers/test/getMockVersionHistory'; import { buildPatientDetails, buildSearchResult } from '../../../../helpers/test/testBuilders'; import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType'; +import * as fhirUtil from '../../../../helpers/utils/fhirUtil'; +import { getObjectUrl } from '../../../../helpers/utils/getPdfObjectUrl'; import { routeChildren, routes } from '../../../../types/generic/routes'; import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types'; import DocumentVersionRestoreHistoryStage from './DocumentVersionRestoreHistoryStage'; @@ -40,6 +42,9 @@ vi.mock('../../../../helpers/requests/getDocument'); vi.mock('../../../../helpers/requests/getDocumentSearchResults'); vi.mock('../../../../helpers/hooks/useTitle'); vi.mock('../../../../helpers/utils/getPdfObjectUrl'); +vi.mock('../../../../helpers/utils/isLocal', () => ({ + isLocal: false, +})); const mockedUsePatient = usePatient as Mock; const mockUseBaseAPIUrl = useBaseAPIUrl as Mock; @@ -47,6 +52,7 @@ const mockUseBaseAPIHeaders = useBaseAPIHeaders as Mock; const mockGetDocumentVersionHistoryResponse = getDocumentVersionHistoryResponse as Mock; const mockGetDocument = getDocument as Mock; const mockGetDocumentSearchResults = getDocumentSearchResults as Mock; +const mockGetObjectUrl = getObjectUrl as Mock; const mockSetDocumentReference = vi.fn(); const mockPatientDetails = buildPatientDetails(); @@ -315,5 +321,129 @@ describe('DocumentVersionRestoreHistoryStage', () => { ); }); }); + + it('navigates to session expired when document loading returns 403', async () => { + const user = userEvent.setup(); + mockGetDocument.mockRejectedValue({ + response: { + status: 403, + }, + }); + + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED); + }); + }); + + it('continues to version history view when document loading returns 404', async () => { + const user = userEvent.setup(); + mockGetDocument.mockRejectedValue({ + response: { + status: 404, + }, + }); + + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect(mockGetObjectUrl).not.toHaveBeenCalled(); + expect(mockSetDocumentReference).toHaveBeenCalledWith(undefined); + }); + + expect(mockNavigate).toHaveBeenCalledWith( + routeChildren.DOCUMENT_VIEW_VERSION_HISTORY, + expect.objectContaining({ + state: expect.objectContaining({ + isActiveVersion: false, + }), + }), + ); + }); + + it('navigates to server error with params when document loading returns 500', async () => { + const user = userEvent.setup(); + mockGetDocument.mockRejectedValue({ + response: { + status: 500, + data: { + err_code: 'ERR500', + interaction_id: 'interaction-123', + }, + }, + }); + + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect( + mockNavigate.mock.calls.some( + ([path]) => + typeof path === 'string' && /^\/server-error\?encodedError=/.test(path), + ), + ).toBe(true); + }); + }); + + it('navigates to server error when getDocumentReferenceFromFhir throws', async () => { + const user = userEvent.setup(); + vi.spyOn(fhirUtil, 'getDocumentReferenceFromFhir').mockImplementation(() => { + throw new Error('invalid fhir reference'); + }); + + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR); + }); + }); + + it('navigates to session expired when handleViewVersion catches a 403 error', async () => { + const user = userEvent.setup(); + vi.spyOn(fhirUtil, 'getDocumentReferenceFromFhir').mockImplementation(() => { + throw { + response: { + status: 403, + }, + }; + }); + + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('view-version-2')).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('view-version-2')); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED); + }); + }); }); });