274 lines
13 KiB
JavaScript
274 lines
13 KiB
JavaScript
import { useMemo, useState } from "react";
|
|
import Link from "next/link";
|
|
|
|
export default function AppealStatusDashboard({ statusObj = [] }) {
|
|
const [openSections, setOpenSections] = useState({});
|
|
|
|
const sortedAppeals = useMemo(
|
|
() =>
|
|
[...statusObj].sort(
|
|
(a, b) => (b.caseCount || 0) - (a.caseCount || 0)
|
|
),
|
|
[statusObj]
|
|
);
|
|
|
|
const percentageOf = (value, total) =>
|
|
total ? `${(((value || 0) / total) * 100).toFixed(1)}%` : "0.0%";
|
|
|
|
const totalCases = sortedAppeals.reduce(
|
|
(sum, appeal) => sum + (appeal.caseCount || 0),
|
|
0
|
|
);
|
|
|
|
const largestAppeal = sortedAppeals[0];
|
|
|
|
const allStatuses = sortedAppeals.flatMap((appeal) =>
|
|
(appeal.statuses || []).map((status) => ({
|
|
...status,
|
|
appealType: appeal.value
|
|
}))
|
|
);
|
|
|
|
const statusTotals = allStatuses.reduce((acc, status) => {
|
|
if (!acc[status.statuscode]) {
|
|
acc[status.statuscode] = {
|
|
value: status.value,
|
|
statuscode: status.statuscode,
|
|
caseCount: 0
|
|
};
|
|
}
|
|
|
|
acc[status.statuscode].caseCount += status.caseCount || 0;
|
|
return acc;
|
|
}, {});
|
|
|
|
const mostCommonStatus = Object.values(statusTotals).sort(
|
|
(a, b) => b.caseCount - a.caseCount
|
|
)[0];
|
|
|
|
const maxAppealCount = largestAppeal?.caseCount || 1;
|
|
|
|
const allExpanded =
|
|
sortedAppeals.length > 0 &&
|
|
sortedAppeals.every((_, index) => openSections[index]);
|
|
|
|
const toggleSection = (index) => {
|
|
setOpenSections((current) => ({
|
|
...current,
|
|
[index]: !current[index]
|
|
}));
|
|
};
|
|
|
|
const showAll = () => {
|
|
const allOpen = {};
|
|
sortedAppeals.forEach((_, index) => {
|
|
allOpen[index] = true;
|
|
});
|
|
setOpenSections(allOpen);
|
|
};
|
|
|
|
const hideAll = () => {
|
|
setOpenSections({});
|
|
};
|
|
|
|
return (
|
|
<div className="govuk-grid-row">
|
|
<div className="govuk-grid-column-full">
|
|
<h1>Appeal case status dashboard</h1>
|
|
|
|
<div className="govuk-grid-row">
|
|
<DashboardCard title="Total cases" value={totalCases} />
|
|
<DashboardCard
|
|
title="Appeal types"
|
|
value={sortedAppeals.length}
|
|
/>
|
|
<DashboardCard
|
|
title="Largest appeal type"
|
|
value={largestAppeal?.caseCount || 0}
|
|
caption={largestAppeal?.value}
|
|
/>
|
|
<DashboardCard
|
|
title="Most common status"
|
|
value={mostCommonStatus?.caseCount || 0}
|
|
caption={mostCommonStatus?.value}
|
|
/>
|
|
</div>
|
|
|
|
<section className="govuk-!-margin-top-8">
|
|
<h2 className="govuk-heading-m">
|
|
Case counts and stage breakdown by appeal type
|
|
</h2>
|
|
|
|
<p className="govuk-body-s">
|
|
Select an appeal type row to show the case stage
|
|
breakdown.
|
|
</p>
|
|
|
|
<div className="govuk-accordion__controls">
|
|
<button
|
|
type="button"
|
|
className="govuk-accordion__show-all"
|
|
onClick={allExpanded ? hideAll : showAll}
|
|
>
|
|
<span className="govuk-accordion__show-all-text">
|
|
{allExpanded
|
|
? "Hide all sections"
|
|
: "Show all sections"}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
className="govuk-accordion appeal-stage-chart"
|
|
id="appeals-with-status-accordion"
|
|
>
|
|
{sortedAppeals.map((appeal, index) => {
|
|
const isOpen = !!openSections[index];
|
|
|
|
const barWidth =
|
|
((appeal.caseCount || 0) / maxAppealCount) *
|
|
100;
|
|
|
|
const sortedStatuses = [...(appeal.statuses || [])]
|
|
.filter((status) => (status.caseCount || 0) > 0)
|
|
.sort(
|
|
(a, b) =>
|
|
(b.caseCount || 0) - (a.caseCount || 0)
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className={
|
|
isOpen
|
|
? "govuk-accordion__section govuk-accordion__section--expanded appeal-stage-chart__accordion-section"
|
|
: "govuk-accordion__section appeal-stage-chart__accordion-section"
|
|
}
|
|
key={appeal.appealType || index}
|
|
>
|
|
<div className="govuk-accordion__section-header">
|
|
<h2 className="govuk-accordion__section-heading">
|
|
<button
|
|
type="button"
|
|
className="govuk-accordion__section-button appeal-stage-chart__button"
|
|
id={`appeal-status-heading-${index}`}
|
|
aria-expanded={isOpen}
|
|
aria-controls={`appeal-status-content-${index}`}
|
|
onClick={() =>
|
|
toggleSection(index)
|
|
}
|
|
>
|
|
<span className="appeal-stage-chart__row">
|
|
<span className="appeal-stage-chart__label">
|
|
{appeal.value} (
|
|
{percentageOf(
|
|
appeal.caseCount,
|
|
totalCases
|
|
)}
|
|
)
|
|
</span>
|
|
|
|
<span className="appeal-stage-chart__bar-wrapper">
|
|
<span
|
|
className="appeal-stage-chart__bar"
|
|
style={{
|
|
width: `${barWidth}%`
|
|
}}
|
|
aria-hidden="true"
|
|
>
|
|
{sortedStatuses.map(
|
|
(
|
|
status,
|
|
statusIndex
|
|
) => (
|
|
<span
|
|
key={`${status.statuscode}-${statusIndex}`}
|
|
className={`appeal-stage-chart__segment appeal-stage-chart__segment--${
|
|
statusIndex %
|
|
6
|
|
}`}
|
|
style={{
|
|
width: `${
|
|
((status.caseCount ||
|
|
0) /
|
|
(appeal.caseCount ||
|
|
1)) *
|
|
100
|
|
}%`
|
|
}}
|
|
title={`${status.value}: ${status.caseCount}`}
|
|
/>
|
|
)
|
|
)}
|
|
</span>
|
|
|
|
<span className="appeal-stage-chart__count">
|
|
{appeal.caseCount ||
|
|
0}{" "}
|
|
cases
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</button>
|
|
</h2>
|
|
</div>
|
|
|
|
<div
|
|
id={`appeal-status-content-${index}`}
|
|
className="govuk-accordion__section-content"
|
|
aria-labelledby={`appeal-status-heading-${index}`}
|
|
hidden={!isOpen}
|
|
>
|
|
<dl className="govuk-summary-list">
|
|
{sortedStatuses.map((status) => (
|
|
<div
|
|
className="govuk-summary-list__row"
|
|
key={status.statuscode}
|
|
>
|
|
<dt className="govuk-summary-list__key">
|
|
<Link
|
|
target="_blank"
|
|
href={`/advancedsearchresults?apt=${appeal.appealType}&statuscode=${status.statuscode}`}
|
|
>
|
|
{status.value}
|
|
</Link>
|
|
</dt>
|
|
|
|
<dd className="govuk-summary-list__value">
|
|
{status.caseCount || 0}{" "}
|
|
cases (
|
|
{percentageOf(
|
|
status.caseCount,
|
|
appeal.caseCount
|
|
)}{" "}
|
|
of this appeal type)
|
|
</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function DashboardCard({ title, value, caption }) {
|
|
return (
|
|
<div className="govuk-grid-column-one-quarter">
|
|
<div className="dashboard-card">
|
|
<p className="govuk-body-s dashboard-card__title">{title}</p>
|
|
<p className="govuk-heading-l dashboard-card__value">{value}</p>
|
|
{caption && (
|
|
<p className="govuk-body-s dashboard-card__caption">
|
|
{caption}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|