Skip to content

Commit e85fd21

Browse files
DTOSS-12464 remove dummy gp code screen
1 parent 46cfec4 commit e85fd21

13 files changed

Lines changed: 1003 additions & 58 deletions

File tree

application/CohortManager/src/Web/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ COHORT_MANAGER_RBAC_CODE=000000000000
2222

2323
# API
2424
EXCEPTIONS_API_URL=https://localhost:3000
25+
REMOVE_DUMMY_GP_CODE_API_URL=https://localhost:3000
2526

2627
# Only required for hosted environments
2728
AUTH_TRUST_HOST=true

application/CohortManager/src/Web/.env.tests

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ EXCEPTIONS_API_URL=http://localhost:3000
1010

1111
# CIS2 RBAC
1212
COHORT_MANAGER_RBAC_CODE=000000000000
13+
14+
# API
15+
REMOVE_DUMMY_GP_CODE_API_URL=http://localhost:3000

application/CohortManager/src/Web/app/components/card.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,40 @@
11
import Link from "next/link";
22

33
interface CardProps {
4-
readonly value: number;
4+
readonly value?: number;
55
readonly label: string;
66
readonly description?: string;
77
readonly url: string;
8+
readonly loading?: boolean;
89
}
910

1011
export default function Card({
1112
value,
1213
label,
1314
description,
1415
url,
16+
loading = false,
1517
}: Readonly<CardProps>) {
18+
const hasValue = value !== undefined;
19+
const isClickable = !loading && (!hasValue || value > 0);
1620
return (
1721
<div
18-
className={`nhsuk-card${value > 0 ? " nhsuk-card--clickable" : ""}`}
22+
className={`nhsuk-card${isClickable ? " nhsuk-card--clickable" : ""}`}
1923
data-testid="card"
2024
>
2125
<div className="nhsuk-card__content">
22-
<p
23-
className="nhsuk-heading-xl nhsuk-u-font-size-64 nhsuk-u-margin-bottom-1"
24-
data-testid="card-number"
25-
>
26-
{value} <span className="nhsuk-u-visually-hidden">{label}</span>
27-
</p>
28-
<h3
29-
className="nhsuk-card__heading nhsuk-heading-m"
30-
data-testid="card-heading"
31-
>
32-
{value > 0 ? (
26+
{loading ? (
27+
<p className="nhsuk-heading-xl nhsuk-u-font-size-64 nhsuk-u-margin-bottom-1" data-testid="card-number">
28+
<span className="app-loading-shimmer" aria-hidden="true">&mdash;</span>
29+
<span className="nhsuk-u-visually-hidden">Loading {label}</span>
30+
</p>
31+
) : hasValue && (
32+
<p className="nhsuk-heading-xl nhsuk-u-font-size-64 nhsuk-u-margin-bottom-1" data-testid="card-number">
33+
{value} <span className="nhsuk-u-visually-hidden">{label}</span>
34+
</p>
35+
)}
36+
<h3 className="nhsuk-card__heading nhsuk-heading-m" data-testid="card-heading">
37+
{isClickable ? (
3338
<Link href={url} className="nhsuk-link--no-visited-state">
3439
{label}
3540
</Link>

application/CohortManager/src/Web/app/components/cardGroup.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import Card from "@/app/components/card";
22

33
interface CardProps {
4-
readonly value: number;
4+
readonly value?: number;
55
readonly label: string;
66
readonly description?: string;
77
readonly url: string;
8+
readonly loading?: boolean;
89
}
910

1011
interface CardGroupProps {
@@ -24,6 +25,7 @@ export default function CardGroup({ items }: Readonly<CardGroupProps>) {
2425
label={card.label}
2526
description={card.description}
2627
url={card.url}
28+
loading={card.loading}
2729
/>
2830
</li>
2931
))}
Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,55 @@
1+
import { Suspense } from "react";
12
import CardGroup from "@/app/components/cardGroup";
2-
import DataError from "@/app/components/dataError";
3-
import { fetchExceptions } from "@/app/lib/fetchExceptions";
3+
import OverviewData from "@/app/components/overviewData";
44

5-
export default async function Overview() {
6-
try {
7-
const notRaisedExceptions = await fetchExceptions({ exceptionStatus: 2 });
8-
const raisedExceptions = await fetchExceptions({ exceptionStatus: 1 });
5+
const skeletonExceptionItems = [
6+
{
7+
label: "Not raised",
8+
description: "Exceptions to be raised with teams",
9+
url: "/exceptions",
10+
loading: true,
11+
},
12+
{
13+
label: "Raised",
14+
description: "Access and amend previously raised exceptions",
15+
url: "/exceptions/raised",
16+
loading: true,
17+
},
18+
];
919

10-
const exceptionItems = [
11-
{
12-
value: notRaisedExceptions.data.TotalItems,
13-
label: "Not raised",
14-
description: "Exceptions to be raised with teams",
15-
url: "/exceptions",
16-
},
17-
{
18-
value: raisedExceptions.data.TotalItems,
19-
label: "Raised",
20-
description: "Access and amend previously raised exceptions",
21-
url: `/exceptions/raised`,
22-
},
23-
];
20+
const reportItems = [
21+
{
22+
value: 28,
23+
label: "Reports",
24+
description: "To manage investigations into demographic changes",
25+
url: "/reports",
26+
},
27+
];
2428

25-
const reportItems = [
26-
{
27-
value: 28, // Showing reports for last 2 weeks
28-
label: "Reports",
29-
description: "To manage investigations into demographic changes",
30-
url: "/reports",
31-
},
32-
];
29+
const dummyGpCodeItems = [
30+
{
31+
label: "Remove Dummy GP Code",
32+
description: "Remove a dummy GP practice code from a participant record",
33+
url: "/remove-dummy-gp-code",
34+
},
35+
];
3336

34-
return (
35-
<main className="nhsuk-main-wrapper" id="maincontent" role="main">
36-
<div className="nhsuk-grid-row">
37-
<div className="nhsuk-grid-column-full">
38-
<h1>Breast screening</h1>
39-
<h2>Exceptions</h2>
40-
<CardGroup items={exceptionItems} />
41-
<h2>Reports</h2>
42-
<CardGroup items={reportItems} />
43-
</div>
37+
export default function Overview() {
38+
return (
39+
<main className="nhsuk-main-wrapper" id="maincontent" role="main">
40+
<div className="nhsuk-grid-row">
41+
<div className="nhsuk-grid-column-full">
42+
<h1>Breast screening</h1>
43+
<h2>Exceptions</h2>
44+
<Suspense fallback={<CardGroup items={skeletonExceptionItems} />}>
45+
<OverviewData />
46+
</Suspense>
47+
<h2>Reports</h2>
48+
<CardGroup items={reportItems} />
49+
<h2>Dummy GP Code</h2>
50+
<CardGroup items={dummyGpCodeItems} />
4451
</div>
45-
</main>
46-
);
47-
} catch {
48-
return <DataError />;
49-
}
52+
</div>
53+
</main>
54+
);
5055
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import CardGroup from "@/app/components/cardGroup";
2+
import DataError from "@/app/components/dataError";
3+
import { fetchExceptions } from "@/app/lib/fetchExceptions";
4+
5+
export default async function OverviewData() {
6+
try {
7+
const [notRaisedExceptions, raisedExceptions] = await Promise.all([
8+
fetchExceptions({ exceptionStatus: 2 }),
9+
fetchExceptions({ exceptionStatus: 1 }),
10+
]);
11+
12+
const exceptionItems = [
13+
{
14+
value: notRaisedExceptions.data.TotalItems,
15+
label: "Not raised",
16+
description: "Exceptions to be raised with teams",
17+
url: "/exceptions",
18+
},
19+
{
20+
value: raisedExceptions.data.TotalItems,
21+
label: "Raised",
22+
description: "Access and amend previously raised exceptions",
23+
url: "/exceptions/raised",
24+
},
25+
];
26+
27+
return <CardGroup items={exceptionItems} />;
28+
} catch {
29+
return <DataError />;
30+
}
31+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"use client";
2+
3+
import { useActionState, useEffect, useState } from "react";
4+
import { removeDummyGpCode, type RemoveDummyGpCodeState } from "@/app/lib/removeDummyGpCode";
5+
6+
export default function RemoveDummyGpCodeForm() {
7+
const [state, formAction] = useActionState<RemoveDummyGpCodeState, FormData>(removeDummyGpCode, null);
8+
const [formKey, setFormKey] = useState(0);
9+
10+
useEffect(() => {
11+
if (state?.error) {
12+
setFormKey((prev) => prev + 1);
13+
}
14+
}, [state]);
15+
16+
const hasError = !!state?.error;
17+
const errorField = state?.field;
18+
const errorMessage = state?.error;
19+
const values = state?.values;
20+
21+
return (
22+
<>
23+
{hasError && (
24+
<div className="nhsuk-error-summary" aria-labelledby="error-summary-title" role="alert" tabIndex={-1} data-testid="error-summary">
25+
<h2 className="nhsuk-error-summary__title" id="error-summary-title">There is a problem</h2>
26+
<div className="nhsuk-error-summary__body">
27+
<ul className="nhsuk-list nhsuk-error-summary__list">
28+
<li>
29+
{errorField ? (
30+
<a href={`#${errorField}`}>{errorMessage}</a>
31+
) : (
32+
<span>{errorMessage}</span>
33+
)}
34+
</li>
35+
</ul>
36+
</div>
37+
</div>
38+
)}
39+
40+
<h1>Remove Dummy GP Code</h1>
41+
<form action={formAction} key={formKey}>
42+
<div className={`nhsuk-form-group ${hasError && errorField === "nhsNumber" ? "nhsuk-form-group--error" : ""}`}>
43+
<label className="nhsuk-label nhsuk-u-font-weight-bold" htmlFor="nhsNumber">NHS Number</label>
44+
<div className="nhsuk-hint" id="nhsNumber-hint">A 10 digit number, for example 485 777 3456</div>
45+
{hasError && errorField === "nhsNumber" && (
46+
<span className="nhsuk-error-message" id="nhsNumber-error">
47+
<span className="nhsuk-u-visually-hidden">Error:</span>{" "}
48+
{errorMessage}
49+
</span>
50+
)}
51+
<input
52+
className={`nhsuk-input nhsuk-input--width-10 ${hasError && errorField === "nhsNumber" ? "nhsuk-input--error" : ""}`}
53+
id="nhsNumber"
54+
name="nhsNumber"
55+
type="text"
56+
inputMode="numeric"
57+
defaultValue={values?.nhsNumber}
58+
aria-describedby={`nhsNumber-hint${hasError && errorField === "nhsNumber" ? " nhsNumber-error" : ""}`}
59+
data-testid="nhs-number-input"
60+
/>
61+
</div>
62+
63+
<div className={`nhsuk-form-group ${hasError && errorField === "forename" ? "nhsuk-form-group--error" : ""}`}>
64+
<label className="nhsuk-label nhsuk-u-font-weight-bold" htmlFor="forename">Forename</label>
65+
{hasError && errorField === "forename" && (
66+
<span className="nhsuk-error-message" id="forename-error">
67+
<span className="nhsuk-u-visually-hidden">Error:</span>{" "}
68+
{errorMessage}
69+
</span>
70+
)}
71+
<input className={`nhsuk-input ${hasError && errorField === "forename" ? "nhsuk-input--error" : ""}`} id="forename" name="forename" type="text" defaultValue={values?.forename} data-testid="forename-input" />
72+
</div>
73+
74+
<div className={`nhsuk-form-group ${hasError && errorField === "surname" ? "nhsuk-form-group--error" : ""}`}>
75+
<label className="nhsuk-label nhsuk-u-font-weight-bold" htmlFor="surname">Surname</label>
76+
{hasError && errorField === "surname" && (
77+
<span className="nhsuk-error-message" id="surname-error">
78+
<span className="nhsuk-u-visually-hidden">Error:</span>{" "}
79+
{errorMessage}
80+
</span>
81+
)}
82+
<input className={`nhsuk-input ${hasError && errorField === "surname" ? "nhsuk-input--error" : ""}`} id="surname" name="surname" type="text" defaultValue={values?.surname} data-testid="surname-input" />
83+
</div>
84+
85+
<div className={`nhsuk-form-group ${hasError && errorField === "dob-day" ? "nhsuk-form-group--error" : ""}`}>
86+
<fieldset className="nhsuk-fieldset" aria-describedby="dob-hint" role="group">
87+
<legend className="nhsuk-fieldset__legend nhsuk-label nhsuk-u-font-weight-bold">Date of Birth</legend>
88+
<div className="nhsuk-hint" id="dob-hint">For example, 15 3 1984</div>
89+
{hasError && errorField === "dob-day" && (
90+
<span className="nhsuk-error-message" id="dob-error">
91+
<span className="nhsuk-u-visually-hidden">Error:</span>{" "}
92+
{errorMessage}
93+
</span>
94+
)}
95+
<div className="nhsuk-date-input" id="dob">
96+
<div className="nhsuk-date-input__item">
97+
<div className="nhsuk-form-group">
98+
<label className="nhsuk-label nhsuk-date-input__label" htmlFor="dob-day">Day</label>
99+
<input className={`nhsuk-input nhsuk-date-input__input nhsuk-input--width-2 ${hasError && errorField === "dob-day" ? "nhsuk-input--error" : ""}`} id="dob-day" name="dob-day" type="text" inputMode="numeric" maxLength={2} defaultValue={values?.dobDay} data-testid="dob-day-input" />
100+
</div>
101+
</div>
102+
<div className="nhsuk-date-input__item">
103+
<div className="nhsuk-form-group">
104+
<label className="nhsuk-label nhsuk-date-input__label" htmlFor="dob-month">Month</label>
105+
<input className={`nhsuk-input nhsuk-date-input__input nhsuk-input--width-2 ${hasError && errorField === "dob-day" ? "nhsuk-input--error" : ""}`} id="dob-month" name="dob-month" type="text" inputMode="numeric" maxLength={2} defaultValue={values?.dobMonth} data-testid="dob-month-input" />
106+
</div>
107+
</div>
108+
<div className="nhsuk-date-input__item">
109+
<div className="nhsuk-form-group">
110+
<label className="nhsuk-label nhsuk-date-input__label" htmlFor="dob-year">Year</label>
111+
<input className={`nhsuk-input nhsuk-date-input__input nhsuk-input--width-4 ${hasError && errorField === "dob-day" ? "nhsuk-input--error" : ""}`} id="dob-year" name="dob-year" type="text" inputMode="numeric" maxLength={4} defaultValue={values?.dobYear} data-testid="dob-year-input" />
112+
</div>
113+
</div>
114+
</div>
115+
</fieldset>
116+
</div>
117+
118+
<div className={`nhsuk-form-group ${hasError && errorField === "serviceNowTicketNumber" ? "nhsuk-form-group--error" : ""}`}>
119+
<label className="nhsuk-label nhsuk-u-font-weight-bold" htmlFor="serviceNowTicketNumber">Service Now Ticket Number</label>
120+
<div className="nhsuk-hint" id="serviceNowTicketNumber-hint">For example, CS1234567</div>
121+
{hasError && errorField === "serviceNowTicketNumber" && (
122+
<span className="nhsuk-error-message" id="serviceNowTicketNumber-error">
123+
<span className="nhsuk-u-visually-hidden">Error:</span>{" "}
124+
{errorMessage}
125+
</span>
126+
)}
127+
<input className={`nhsuk-input nhsuk-input--width-10 ${hasError && errorField === "serviceNowTicketNumber" ? "nhsuk-input--error" : ""}`} id="serviceNowTicketNumber" name="serviceNowTicketNumber" type="text" defaultValue={values?.serviceNowTicketNumber} aria-describedby="serviceNowTicketNumber-hint" data-testid="service-now-ticket-input"/>
128+
</div>
129+
130+
<button className="nhsuk-button" data-module="nhsuk-button" type="submit" data-testid="submit-button">Submit</button>
131+
</form>
132+
</>
133+
);
134+
}

application/CohortManager/src/Web/app/globals.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,24 @@
148148
}
149149

150150
// Header
151+
152+
// Loading shimmer for card placeholders
153+
.app-loading-shimmer {
154+
display: inline-block;
155+
color: transparent;
156+
background: linear-gradient(90deg, #d8dde0 25%, #e8ebee 50%, #d8dde0 75%);
157+
background-size: 200% 100%;
158+
animation: app-shimmer 1.5s ease-in-out infinite;
159+
border-radius: 4px;
160+
}
161+
@keyframes app-shimmer {
162+
0% {
163+
background-position: 200% 0;
164+
}
165+
100% {
166+
background-position: -200% 0;
167+
}
168+
}
151169
.nhsuk-header__content {
152170
display: flex;
153171
align-items: center;

0 commit comments

Comments
 (0)