Skip to content

Commit

Permalink
Study overview page rework (#11)
Browse files Browse the repository at this point in the history
* Device deployment card

* Inactive Deployments card

* DeploymentsInProgress card

* Fix navigation url on cards

* Cleanup

* Remove kotlinx value datetime
  • Loading branch information
jakdan99 authored Sep 23, 2024
1 parent cfdd6ce commit 2a88e5c
Show file tree
Hide file tree
Showing 33 changed files with 1,055 additions and 613 deletions.
File renamed without changes.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@mui/material": "^5.15.20",
"@mui/styles": "^5.15.20",
"@mui/system": "^5.15.20",
"@mui/x-charts": "^7.7.0",
"@mui/x-date-pickers": "^7.6.2",
"@tanstack/react-query": "^5.45.0",
"@tanstack/react-query-devtools": "^5.45.0",
Expand Down
381 changes: 376 additions & 5 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/components/PieCenterLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useDrawingArea } from "@mui/x-charts";
import { StyledNumberTSpan, StyledTSpan } from "./styles";

const PieCenterLabel = ({ children }: { children: React.ReactNode }) => {
const { width, height, left, top } = useDrawingArea();
return (
<text x={left + width / 2} y={top + height / 2}>
<StyledNumberTSpan x={left + width} dy="-0.3em">
{children}
</StyledNumberTSpan>
<StyledTSpan x={left + width} dy="2em">
Deployments
</StyledTSpan>
</text>
);
};
export default PieCenterLabel;
21 changes: 21 additions & 0 deletions src/components/PieCenterLabel/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { styled } from "@Utils/theme";

export const StyledTSpan = styled("tspan")(({ theme }) => ({
fill: theme.palette.text.primary,
font: theme.typography.fontFamily,
fontWeight: 600,
lineHeight: "16px",
textAnchor: "middle",
dominantBaseline: "central",
fontSize: 12,
}));

export const StyledNumberTSpan = styled("tspan")(({ theme }) => ({
fill: theme.palette.text.primary,
font: theme.typography.fontFamily,
fontWeight: 600,
lineHeight: "32px",
textAnchor: "middle",
dominantBaseline: "central",
fontSize: 24,
}));
4 changes: 1 addition & 3 deletions src/pages/Studies/StudyCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ const StudyCard = ({ onClick, study, status, description }: Props) => {
<CardDescription variant="body2">{description}</CardDescription>
<Bottom>
<CreationText variant="h5">
{formatDateTime(
study.createdOn.toString(),
)}
{formatDateTime(study.createdOn.toString())}
</CreationText>
{/* TODO: Add real owner name after backend supports it */}
{/* <CreationText variant="h5">Jakob</CreationText> */}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Typography } from "@mui/material";
import { Stack } from "@mui/system";
import { PieValueType } from "@mui/x-charts";
import ParticipantsRow from "./styles";

const DeploymentStatusLegend = ({ data }: { data: PieValueType[] }) => {
return (
<Stack direction="column">
{data.map((entry) => (
<ParticipantsRow key={entry.id}>
<Typography variant="h3" color={entry.color} display="flex">
{entry.value}
</Typography>
<Typography variant="h3" color={entry.color} display="flex">
{`${entry.label}`}
</Typography>
</ParticipantsRow>
))}
</Stack>
);
};

export default DeploymentStatusLegend;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { styled } from "@Utils/theme";

const ParticipantsRow = styled("div")({
paddingBottom: 10,
marginBottom: 0,
display: "grid",
gridTemplateColumns: "80px 1fr",
gap: 8,
alignItems: "end",
});

export default ParticipantsRow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { getDeploymentStatusColor } from "@Utils/utility";
import { Typography } from "@mui/material";
import { Stack } from "@mui/system";

const TooltipContent = () => {
return (
<Stack direction="column" spacing="8px">
<Typography variant="h5">
<span style={{ color: getDeploymentStatusColor("Invited") }}>
Invited
</span>
: Indicates that the invited participants have not yet acted on the
invitation.
</Typography>
<Typography variant="h5">
<span style={{ color: getDeploymentStatusColor("Deploying") }}>
Deploying
</span>
: Participants have started registering devices, but are remaining
devices.
</Typography>
<Typography variant="h5">
<span style={{ color: getDeploymentStatusColor("Running") }}>
Running
</span>
: All the devices have been deployed and started the data collection.
</Typography>
<Typography variant="h5">
<span style={{ color: getDeploymentStatusColor("Stopped") }}>
Stopped
</span>
: The study deployment has been stopped and no more data will be
collected.
</Typography>
</Stack>
);
};

export default TooltipContent;
147 changes: 147 additions & 0 deletions src/pages/StudyOverview/Overview/DeploymentStatus/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import CarpErrorCardComponent from "@Components/CarpErrorCardComponent";
import PieCenterLabel from "@Components/PieCenterLabel";
import { useParticipantsStatus } from "@Utils/queries/participants";
import { getDeploymentStatusColor } from "@Utils/utility";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ManageAccountsIcon from "@mui/icons-material/ManageAccounts";
import { Typography } from "@mui/material";
import { Stack } from "@mui/system";
import { PieValueType } from "@mui/x-charts";
import { PieChart } from "@mui/x-charts/PieChart";
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router";
import LoadingSkeleton from "../LoadingSkeleton";
import DeploymentStatusLegend from "./DeploymentStatusLegend";
import TooltipContent from "./TooltipContent";
import {
StyledButton,
StyledCard,
StyledTitle,
StyledTooltip,
Top,
} from "./styles";

const DeploymentStatus = () => {
const navigate = useNavigate();
const { id: studyId } = useParams();
const {
data: participantStatus,
isLoading: participantStatusLoading,
error: participantStatusError,
} = useParticipantsStatus(studyId);

const [statuses, setStatuses] = useState<PieValueType[]>([]);

useEffect(() => {
if (!participantStatus) return;
const data = participantStatus.reduce(
(acc, curr) => {
acc[
curr.constructor.name.toLocaleLowerCase().replace("_0", "")
].value += 1;
return acc;
},
{
invited: {
id: 0,
value: 0,
label: "Invited",
color: getDeploymentStatusColor("Invited"),
},
inDeployment: {
id: 1,
value: 0,
label: "Deploying",
color: getDeploymentStatusColor("Deploying"),
},
running: {
id: 2,
value: 0,
label: "Running",
color: getDeploymentStatusColor("Running"),
},
stopped: {
id: 3,
value: 0,
label: "Stopped",
color: getDeploymentStatusColor("Stopped"),
},
},
);
setStatuses(Object.values(data));
}, [participantStatus]);

if (participantStatusLoading) return <LoadingSkeleton />;
if (participantStatusError)
return (
<CarpErrorCardComponent
message="An error occurred while loading study status"
error={participantStatusError}
/>
);

return (
<StyledCard>
<Top>
<StyledTitle variant="h2">
Deployment Status
<StyledTooltip
title={TooltipContent()}
placement="right-start"
componentsProps={{
tooltip: {
sx: {
color: "text.primary",
backgroundColor: "#FFF",
boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.25)",
},
},
}}
>
<InfoOutlinedIcon />
</StyledTooltip>
</StyledTitle>
<StyledButton
onClick={() =>
navigate(`/studies/${studyId}/participants/deployments`)
}
variant="outlined"
>
<ManageAccountsIcon fontSize="small" color="primary" />
<Typography variant="h5">Manage</Typography>
</StyledButton>
</Top>
<Stack direction="row" alignItems="center">
<div style={{ width: "200px", height: "200px", display: "flex" }}>
<PieChart
height={200}
width={200}
series={[
{
data: statuses,
cx: 100,
innerRadius: 50,
outerRadius: 90,
cornerRadius: 5,
paddingAngle: 0,
startAngle: 0,
endAngle: -180,
},
]}
slotProps={{
legend: {
hidden: true,
},
}}
tooltip={{ trigger: "none" }}
>
<PieCenterLabel>{participantStatus.length}</PieCenterLabel>
</PieChart>
</div>
<DeploymentStatusLegend data={statuses} />
</Stack>
</StyledCard>
);
};

export default DeploymentStatus;
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Button, Card, Typography } from "@mui/material";
import { Button, Card, Tooltip, Typography } from "@mui/material";
import { styled } from "@Utils/theme";

export const StyledCard = styled(Card)({
display: "flex",
flexDirection: "column",
padding: 24,
height: 288,
borderRadius: 16,
});

export const StyledTooltip = styled(Tooltip)(({ theme }) => ({
color: theme.palette.grey[500],
"&:hover": {
color: theme.palette.primary.main,
},
}));

export const Top = styled("div")({
display: "flex",
flexDirection: "row",
Expand All @@ -27,18 +33,9 @@ export const StyledButton = styled(Button)(({ theme }) => ({

export const StyledTitle = styled(Typography)(({ theme }) => ({
color: theme.palette.primary.main,
}));

export const ParticipantsRow = styled("div", {
shouldForwardProp: (prop) => prop !== "isFirst",
})<{ isFirst?: boolean }>(({ isFirst, theme }) => ({
borderBottom: isFirst ? `1px solid ${theme.palette.grey[500]}` : "none",
paddingBottom: isFirst ? 10 : 0,
marginBottom: isFirst ? 4 : 0,
display: "grid",
gridTemplateColumns: "80px 1fr",
display: "flex",
alignItems: "center",
gap: 8,
alignItems: "end",
}));

export const StyledNumber = styled(Typography, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Typography } from "@mui/material";
import { Stack } from "@mui/system";
import StyledStatusDot from "./styles";

const TooltipContent = () => {
return (
<Stack direction="column" spacing="12px">
<Stack spacing="4px" direction="row" alignItems="baseline">
<StyledStatusDot status="Unregistered" />
<Typography variant="h5">
Unregistered: The device has not been registered.
</Typography>
</Stack>
<Stack spacing="4px" direction="row" alignItems="baseline">
<StyledStatusDot status="Registered" />
<Typography variant="h5">
Registered: The device has been registered.
</Typography>
</Stack>
<Stack spacing="4px" direction="row" alignItems="baseline">
<StyledStatusDot status="Deployed" />
<Typography variant="h5">
Deployed: The device was able to load all the necessary plugins to
execute the study.
</Typography>
</Stack>
<Stack spacing="4px" direction="row" alignItems="baseline">
<StyledStatusDot status="NeedsRedeployment" />
<Typography variant="h5">
Needs Redeployment: The device has previously been deployed correctly,
but due to changes in the device registration needs to be redeployed.
</Typography>
</Stack>
</Stack>
);
};

export default TooltipContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { styled } from "@mui/system";
import { getDeviceStatusColor } from "@Utils/utility";

const StyledStatusDot = styled("div", {
shouldForwardProp: (prop) => prop !== "status",
})<{ status?: string }>(({ status }) => ({
width: 8,
height: 8,
minWidth: 8,
borderRadius: "50%",
backgroundColor: getDeviceStatusColor(status),
}));

export default StyledStatusDot;
Loading

0 comments on commit 2a88e5c

Please sign in to comment.