From 2a88e5c9131d25eec13e371df85dd4fe15766c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Jakab?= <61331998+jakdan99@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:19:49 +0200 Subject: [PATCH] Study overview page rework (#11) * Device deployment card * Inactive Deployments card * DeploymentsInProgress card * Fix navigation url on cards * Cleanup * Remove kotlinx value datetime --- .prettierrc.js => .prettierrc.mjs | 0 package.json | 1 + pnpm-lock.yaml | 381 +++++++++++++++++- src/components/PieCenterLabel/index.tsx | 17 + src/components/PieCenterLabel/styles.ts | 21 + src/pages/Studies/StudyCard/index.tsx | 4 +- .../DeploymentStatusLegend/index.tsx | 23 ++ .../DeploymentStatusLegend/styles.ts | 12 + .../DeploymentStatus/TooltipContent/index.tsx | 39 ++ .../Overview/DeploymentStatus/index.tsx | 147 +++++++ .../styles.ts | 23 +- .../TooltipContent/index.tsx | 38 ++ .../TooltipContent/styles.ts | 14 + .../Overview/DeploymentsInProgress/index.tsx | 149 +++++++ .../styles.ts | 20 +- .../Overview/DeviceDeploymentStatus/index.tsx | 169 -------- .../Overview/InactiveDeployments/index.tsx | 141 +++++++ .../styles.ts | 17 +- .../Overview/InactiveParticipants/index.tsx | 161 -------- .../Overview/Participants/index.tsx | 169 -------- .../StudyOverview/Overview/Status/index.tsx | 11 + .../StudyOverview/Overview/Status/styles.ts | 3 +- .../Overview/StudyData/index.tsx | 15 - .../Overview/StudyData/styles.ts | 20 - .../Overview/StudyDataTypes/index.tsx | 14 - .../Overview/StudyDataTypes/styles.ts | 19 - src/pages/StudyOverview/Overview/index.tsx | 14 +- src/pages/StudyOverview/Overview/styles.ts | 2 +- src/pages/StudySettings/StudyData/index.tsx | 2 +- src/utils/queries/participants.ts | 9 + src/utils/queries/studies.ts | 10 +- src/utils/theme.tsx | 1 + src/utils/utility.tsx | 2 + 33 files changed, 1055 insertions(+), 613 deletions(-) rename .prettierrc.js => .prettierrc.mjs (100%) create mode 100644 src/components/PieCenterLabel/index.tsx create mode 100644 src/components/PieCenterLabel/styles.ts create mode 100644 src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/index.tsx create mode 100644 src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/styles.ts create mode 100644 src/pages/StudyOverview/Overview/DeploymentStatus/TooltipContent/index.tsx create mode 100644 src/pages/StudyOverview/Overview/DeploymentStatus/index.tsx rename src/pages/StudyOverview/Overview/{Participants => DeploymentStatus}/styles.ts (74%) create mode 100644 src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/index.tsx create mode 100644 src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/styles.ts create mode 100644 src/pages/StudyOverview/Overview/DeploymentsInProgress/index.tsx rename src/pages/StudyOverview/Overview/{DeviceDeploymentStatus => DeploymentsInProgress}/styles.ts (82%) delete mode 100644 src/pages/StudyOverview/Overview/DeviceDeploymentStatus/index.tsx create mode 100644 src/pages/StudyOverview/Overview/InactiveDeployments/index.tsx rename src/pages/StudyOverview/Overview/{InactiveParticipants => InactiveDeployments}/styles.ts (78%) delete mode 100644 src/pages/StudyOverview/Overview/InactiveParticipants/index.tsx delete mode 100644 src/pages/StudyOverview/Overview/Participants/index.tsx delete mode 100644 src/pages/StudyOverview/Overview/StudyData/index.tsx delete mode 100644 src/pages/StudyOverview/Overview/StudyData/styles.ts delete mode 100644 src/pages/StudyOverview/Overview/StudyDataTypes/index.tsx delete mode 100644 src/pages/StudyOverview/Overview/StudyDataTypes/styles.ts diff --git a/.prettierrc.js b/.prettierrc.mjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.mjs diff --git a/package.json b/package.json index 8f7aeb3..18c0609 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adae092..a25725f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.0.1 version: 1.0.1(axios@1.7.2)(oidc-client-ts@3.0.1)(react-oidc-context@3.1.0(oidc-client-ts@3.0.1)(react@18.3.1))(react@18.3.1) '@carp-dk/client': - specifier: 1.1.0 - version: 1.1.0 + specifier: 1.2.0 + version: 1.2.0 '@emotion/react': specifier: ^11.11.4 version: 11.11.4(@types/react@18.3.3)(react@18.3.1) @@ -47,6 +47,9 @@ importers: '@mui/system': specifier: ^5.15.20 version: 5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/x-charts': + specifier: ^7.7.0 + version: 7.12.0(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-date-pickers': specifier: ^7.6.2 version: 7.7.1(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -281,6 +284,10 @@ packages: resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.0': + resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.7': resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} @@ -301,8 +308,8 @@ packages: react: '>=16.8.0' react-oidc-context: ^2.3.1 - '@carp-dk/client@1.1.0': - resolution: {integrity: sha512-m1FO/2dMO4fhfSN7fzGOynBTfRahgBy8uk2NeO/v5qyMFzp2+v3HXVOssdheMGAK5Bl9656BOQwbKBsYV2w5ZQ==} + '@carp-dk/client@1.2.0': + resolution: {integrity: sha512-b48+yhXWFbAfkMaImlq8ibJgt75idjLdQvwXJUPduFn96Xj6E8fEYNNcHG0iTtDovGmymyxGEIl+ikePlD4hQg==} '@carp-dk/eslint-config@1.1.0': resolution: {integrity: sha512-vIz9PJZBaSadbe3rO9nTJQoa01c8h9judJv1uVDYjlApienWU8W22Yi8aBJLHil3wI9dFXTPcDvx1yqMpkOo+w==} @@ -671,6 +678,16 @@ packages: '@types/react': optional: true + '@mui/private-theming@5.16.6': + resolution: {integrity: sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/styled-engine@5.15.14': resolution: {integrity: sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==} engines: {node: '>=12.0.0'} @@ -684,6 +701,19 @@ packages: '@emotion/styled': optional: true + '@mui/styled-engine@5.16.6': + resolution: {integrity: sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/styles@5.15.20': resolution: {integrity: sha512-zpXYhNxQ9A4zxF3IRQRZRUg7fXYj6Wfa3nB+7yOLVecokhjCAr1zY2VC5Uznf5qs2cfgBRfmDkBYqvQjHWf2uA==} engines: {node: '>=12.0.0'} @@ -710,6 +740,22 @@ packages: '@types/react': optional: true + '@mui/system@5.16.6': + resolution: {integrity: sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + '@mui/types@7.2.14': resolution: {integrity: sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==} peerDependencies: @@ -718,6 +764,14 @@ packages: '@types/react': optional: true + '@mui/types@7.2.15': + resolution: {integrity: sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/utils@5.15.20': resolution: {integrity: sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==} engines: {node: '>=12.0.0'} @@ -728,6 +782,34 @@ packages: '@types/react': optional: true + '@mui/utils@5.16.6': + resolution: {integrity: sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/x-charts-vendor@7.12.0': + resolution: {integrity: sha512-05J1o0T3/7iNd0I4LnXLjGwxybzJN8hF2qr/n8XduJYWxYIXRbGsD/Y0nVnHh/EjIGe3aHqCYbt1Ob9E/RQUtQ==} + + '@mui/x-charts@7.12.0': + resolution: {integrity: sha512-N0Q83vXinNsdVJ4l8f1WcPdbsSxO7iOIvwn6URW/iCvDx6ZxZOxw2AKAerM5l2lPr9N3G8dYWMz5aG3DrVpNjg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/x-date-pickers@7.7.1': resolution: {integrity: sha512-p7/TY8QcdQd6RelNqzW5q89GeUFctvZnDHTfQVEC0l0nAy7ArE6u21uNF8QWGrijZoJXCM+OlIRzlZADaUPpWA==} engines: {node: '>=14.0.0'} @@ -787,6 +869,33 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@react-spring/animated@9.7.4': + resolution: {integrity: sha512-7As+8Pty2QlemJ9O5ecsuPKjmO0NKvmVkRR1n6mEotFgWar8FKuQt2xgxz3RTgxcccghpx1YdS1FCdElQNexmQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/core@9.7.4': + resolution: {integrity: sha512-GzjA44niEJBFUe9jN3zubRDDDP2E4tBlhNlSIkTChiNf9p4ZQlgXBg50qbXfSXHQPHak/ExYxwhipKVsQ/sUTw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/rafz@9.7.4': + resolution: {integrity: sha512-mqDI6rW0Ca8IdryOMiXRhMtVGiEGLIO89vIOyFQXRIwwIMX30HLya24g9z4olDvFyeDW3+kibiKwtZnA4xhldA==} + + '@react-spring/shared@9.7.4': + resolution: {integrity: sha512-bEPI7cQp94dOtCFSEYpxvLxj0+xQfB5r9Ru1h8OMycsIq7zFZon1G0sHrBLaLQIWeMCllc4tVDYRTLIRv70C8w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/types@9.7.4': + resolution: {integrity: sha512-iQVztO09ZVfsletMiY+DpT/JRiBntdsdJ4uqk3UJFhrhS8mIC9ZOZbmfGSRs/kdbNPQkVyzucceDicQ/3Mlj9g==} + + '@react-spring/web@9.7.4': + resolution: {integrity: sha512-UMvCZp7I5HCVIleSa4BwbNxynqvj+mJjG2m20VO2yPoi2pnCYANy58flvz9v/YcXTAvsmL655FV3pm5fbr6akA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@remix-run/router@1.17.0': resolution: {integrity: sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==} engines: {node: '>=14.0.0'} @@ -937,6 +1046,27 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.0': + resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} + + '@types/d3-scale@4.0.8': + resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} + + '@types/d3-shape@3.1.6': + resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + + '@types/d3-time@3.0.3': + resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1370,6 +1500,46 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -1432,6 +1602,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1984,6 +2157,10 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2719,6 +2896,9 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.18.0: resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3301,6 +3481,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.25.0': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 @@ -3335,7 +3519,7 @@ snapshots: react: 18.3.1 react-oidc-context: 3.1.0(oidc-client-ts@3.0.1)(react@18.3.1) - '@carp-dk/client@1.1.0': {} + '@carp-dk/client@1.2.0': {} ? '@carp-dk/eslint-config@1.1.0(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0))(eslint@8.57.0))(eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.34.3(eslint@8.57.0))(eslint@8.57.0))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0))(eslint-plugin-prefer-arrow@1.2.3(eslint@8.57.0))(eslint-plugin-prettier@5.0.0-alpha.2(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.2))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react-refresh@0.4.7(eslint@8.57.0))(eslint-plugin-react@7.34.3(eslint@8.57.0))(eslint-plugin-unused-imports@4.0.0(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0))(eslint@8.57.0)(prettier-plugin-organize-imports@3.2.4(prettier@3.3.2)(typescript@5.5.2))(prettier@3.3.2)' : dependencies: @@ -3680,6 +3864,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@mui/private-theming@5.16.6(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.0 + '@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + '@mui/styled-engine@5.15.14(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -3691,6 +3884,17 @@ snapshots: '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/styled-engine@5.16.6(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.0 + '@emotion/cache': 11.11.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/styles@5.15.20(@types/react@18.3.3)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -3730,10 +3934,30 @@ snapshots: '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 + '@mui/system@5.16.6(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.0 + '@mui/private-theming': 5.16.6(@types/react@18.3.3)(react@18.3.1) + '@mui/styled-engine': 5.16.6(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.15(@types/react@18.3.3) + '@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + '@mui/types@7.2.14(@types/react@18.3.3)': optionalDependencies: '@types/react': 18.3.3 + '@mui/types@7.2.15(@types/react@18.3.3)': + optionalDependencies: + '@types/react': 18.3.3 + '@mui/utils@5.15.20(@types/react@18.3.3)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -3744,6 +3968,55 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@mui/utils@5.16.6(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.0 + '@mui/types': 7.2.15(@types/react@18.3.3) + '@types/prop-types': 15.7.12 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/x-charts-vendor@7.12.0': + dependencies: + '@babel/runtime': 7.25.0 + '@types/d3-color': 3.1.3 + '@types/d3-delaunay': 6.0.4 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.8 + '@types/d3-shape': 3.1.6 + '@types/d3-time': 3.0.3 + d3-color: 3.1.0 + d3-delaunay: 6.0.4 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + delaunator: 5.0.1 + robust-predicates: 3.0.2 + + '@mui/x-charts@7.12.0(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.0 + '@mui/material': 5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/system': 5.16.6(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/utils': 5.16.6(@types/react@18.3.3)(react@18.3.1) + '@mui/x-charts-vendor': 7.12.0 + '@react-spring/rafz': 9.7.4 + '@react-spring/web': 9.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + '@mui/x-date-pickers@7.7.1(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -3783,6 +4056,38 @@ snapshots: '@popperjs/core@2.11.8': {} + '@react-spring/animated@9.7.4(react@18.3.1)': + dependencies: + '@react-spring/shared': 9.7.4(react@18.3.1) + '@react-spring/types': 9.7.4 + react: 18.3.1 + + '@react-spring/core@9.7.4(react@18.3.1)': + dependencies: + '@react-spring/animated': 9.7.4(react@18.3.1) + '@react-spring/shared': 9.7.4(react@18.3.1) + '@react-spring/types': 9.7.4 + react: 18.3.1 + + '@react-spring/rafz@9.7.4': {} + + '@react-spring/shared@9.7.4(react@18.3.1)': + dependencies: + '@react-spring/rafz': 9.7.4 + '@react-spring/types': 9.7.4 + react: 18.3.1 + + '@react-spring/types@9.7.4': {} + + '@react-spring/web@9.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-spring/animated': 9.7.4(react@18.3.1) + '@react-spring/core': 9.7.4(react@18.3.1) + '@react-spring/shared': 9.7.4(react@18.3.1) + '@react-spring/types': 9.7.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@remix-run/router@1.17.0': {} '@rollup/plugin-alias@5.1.0(rollup@4.18.0)': @@ -3900,6 +4205,26 @@ snapshots: dependencies: '@babel/types': 7.24.7 + '@types/d3-color@3.1.3': {} + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.0': {} + + '@types/d3-scale@4.0.8': + dependencies: + '@types/d3-time': 3.0.3 + + '@types/d3-shape@3.1.6': + dependencies: + '@types/d3-path': 3.1.0 + + '@types/d3-time@3.0.3': {} + '@types/estree@1.0.5': {} '@types/history@4.7.11': {} @@ -4412,6 +4737,44 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-format@3.1.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.1: @@ -4485,6 +4848,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -5223,6 +5590,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 + internmap@2.0.3: {} + ipaddr.js@1.9.1: {} is-arguments@1.1.1: @@ -5928,6 +6297,8 @@ snapshots: dependencies: glob: 7.2.3 + robust-predicates@3.0.2: {} + rollup@4.18.0: dependencies: '@types/estree': 1.0.5 diff --git a/src/components/PieCenterLabel/index.tsx b/src/components/PieCenterLabel/index.tsx new file mode 100644 index 0000000..767ddf4 --- /dev/null +++ b/src/components/PieCenterLabel/index.tsx @@ -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 ( + + + {children} + + + Deployments + + + ); +}; +export default PieCenterLabel; diff --git a/src/components/PieCenterLabel/styles.ts b/src/components/PieCenterLabel/styles.ts new file mode 100644 index 0000000..4d848e9 --- /dev/null +++ b/src/components/PieCenterLabel/styles.ts @@ -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, +})); diff --git a/src/pages/Studies/StudyCard/index.tsx b/src/pages/Studies/StudyCard/index.tsx index b6aefca..220fbd4 100644 --- a/src/pages/Studies/StudyCard/index.tsx +++ b/src/pages/Studies/StudyCard/index.tsx @@ -33,9 +33,7 @@ const StudyCard = ({ onClick, study, status, description }: Props) => { {description} - {formatDateTime( - study.createdOn.toString(), - )} + {formatDateTime(study.createdOn.toString())} {/* TODO: Add real owner name after backend supports it */} {/* Jakob */} diff --git a/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/index.tsx b/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/index.tsx new file mode 100644 index 0000000..895baad --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/index.tsx @@ -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 ( + + {data.map((entry) => ( + + + {entry.value} + + + {`${entry.label}`} + + + ))} + + ); +}; + +export default DeploymentStatusLegend; diff --git a/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/styles.ts b/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/styles.ts new file mode 100644 index 0000000..a4f6677 --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentStatus/DeploymentStatusLegend/styles.ts @@ -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; diff --git a/src/pages/StudyOverview/Overview/DeploymentStatus/TooltipContent/index.tsx b/src/pages/StudyOverview/Overview/DeploymentStatus/TooltipContent/index.tsx new file mode 100644 index 0000000..a322cb7 --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentStatus/TooltipContent/index.tsx @@ -0,0 +1,39 @@ +import { getDeploymentStatusColor } from "@Utils/utility"; +import { Typography } from "@mui/material"; +import { Stack } from "@mui/system"; + +const TooltipContent = () => { + return ( + + + + Invited + + : Indicates that the invited participants have not yet acted on the + invitation. + + + + Deploying + + : Participants have started registering devices, but are remaining + devices. + + + + Running + + : All the devices have been deployed and started the data collection. + + + + Stopped + + : The study deployment has been stopped and no more data will be + collected. + + + ); +}; + +export default TooltipContent; diff --git a/src/pages/StudyOverview/Overview/DeploymentStatus/index.tsx b/src/pages/StudyOverview/Overview/DeploymentStatus/index.tsx new file mode 100644 index 0000000..8838e84 --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentStatus/index.tsx @@ -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([]); + + 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 ; + if (participantStatusError) + return ( + + ); + + return ( + + + + Deployment Status + + + + + + navigate(`/studies/${studyId}/participants/deployments`) + } + variant="outlined" + > + + Manage + + + +
+ + {participantStatus.length} + +
+ +
+
+ ); +}; + +export default DeploymentStatus; diff --git a/src/pages/StudyOverview/Overview/Participants/styles.ts b/src/pages/StudyOverview/Overview/DeploymentStatus/styles.ts similarity index 74% rename from src/pages/StudyOverview/Overview/Participants/styles.ts rename to src/pages/StudyOverview/Overview/DeploymentStatus/styles.ts index 45924ee..8fb9000 100644 --- a/src/pages/StudyOverview/Overview/Participants/styles.ts +++ b/src/pages/StudyOverview/Overview/DeploymentStatus/styles.ts @@ -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", @@ -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, { diff --git a/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/index.tsx b/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/index.tsx new file mode 100644 index 0000000..ceb58cc --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/index.tsx @@ -0,0 +1,38 @@ +import { Typography } from "@mui/material"; +import { Stack } from "@mui/system"; +import StyledStatusDot from "./styles"; + +const TooltipContent = () => { + return ( + + + + + Unregistered: The device has not been registered. + + + + + + Registered: The device has been registered. + + + + + + Deployed: The device was able to load all the necessary plugins to + execute the study. + + + + + + Needs Redeployment: The device has previously been deployed correctly, + but due to changes in the device registration needs to be redeployed. + + + + ); +}; + +export default TooltipContent; diff --git a/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/styles.ts b/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/styles.ts new file mode 100644 index 0000000..251b17c --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentsInProgress/TooltipContent/styles.ts @@ -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; diff --git a/src/pages/StudyOverview/Overview/DeploymentsInProgress/index.tsx b/src/pages/StudyOverview/Overview/DeploymentsInProgress/index.tsx new file mode 100644 index 0000000..b795272 --- /dev/null +++ b/src/pages/StudyOverview/Overview/DeploymentsInProgress/index.tsx @@ -0,0 +1,149 @@ +/* eslint-disable no-underscore-dangle */ +import CarpErrorCardComponent from "@Components/CarpErrorCardComponent"; +import { useParticipantGroupsAccountsAndStatus } from "@Utils/queries/participants"; +import { + Table, + TableBody, + TableContainer, + TableHead, + Typography, +} from "@mui/material"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { Stack } from "@mui/system"; +import LoadingSkeleton from "../LoadingSkeleton"; +import { + HeaderTableCell, + HeaderText, + SecondaryCellText, + StyledCard, + StyledDescription, + StyledStatusDot, + StyledTableCell, + StyledTableRow, + StyledTitle, + StyledTooltip, +} from "./styles"; +import TooltipContent from "./TooltipContent"; + +const DeploymentsInProgress = () => { + const { id: studyId } = useParams(); + const { + data: deploymentsAccountAndStatus, + isLoading: isDeploymentsAccountAndStatusLoading, + error: deploymentsAccountAndStatusError, + } = useParticipantGroupsAccountsAndStatus(studyId); + const [deploymentProgress, setDeploymentProgress] = useState< + { + deploymentId: string; + devices: any[]; + }[] + >([]); + + useEffect(() => { + if ( + deploymentsAccountAndStatus?.groups !== undefined && + deploymentsAccountAndStatus?.groups.length !== 0 + ) { + const deployments = deploymentsAccountAndStatus.groups + .filter((g) => !g.deploymentStatus.__type.includes("Stopped")) + .map((g) => { + const devices = g.deploymentStatus.deviceStatusList.filter( + (dl) => dl.device.isPrimaryDevice, + ); + return { deploymentId: g.participantGroupId, devices }; + }) + .flat(); + setDeploymentProgress(deployments); + } + }, [deploymentsAccountAndStatus]); + + if (isDeploymentsAccountAndStatusLoading) { + return ; + } + + if (deploymentsAccountAndStatusError) { + return ( + + ); + } + + return ( + + + Deployments in Progress + + + + + + The status of the master devices, for each Deployment. Select the + Deployment ID for further information. + + + + + + + Deployment ID + + + Device Registration + + + + + {deploymentProgress.map((g) => ( + + + + {`... ${g.deploymentId.slice(-4)}`} + + + + + {g.devices.map((d) => ( + + + + {d.device.roleName} + + + ))} + + + + ))} + +
+
+
+ ); +}; + +export default DeploymentsInProgress; diff --git a/src/pages/StudyOverview/Overview/DeviceDeploymentStatus/styles.ts b/src/pages/StudyOverview/Overview/DeploymentsInProgress/styles.ts similarity index 82% rename from src/pages/StudyOverview/Overview/DeviceDeploymentStatus/styles.ts rename to src/pages/StudyOverview/Overview/DeploymentsInProgress/styles.ts index c77ab37..3ea58fa 100644 --- a/src/pages/StudyOverview/Overview/DeviceDeploymentStatus/styles.ts +++ b/src/pages/StudyOverview/Overview/DeploymentsInProgress/styles.ts @@ -1,18 +1,27 @@ -import { Card, TableCell, TableRow, Typography } from "@mui/material"; +import { Card, TableCell, TableRow, Tooltip, Typography } from "@mui/material"; import { styled } from "@Utils/theme"; import { getDeviceStatusColor } from "@Utils/utility"; export const StyledCard = styled(Card)({ display: "flex", flexDirection: "column", - gridColumn: "span 2", padding: 24, - height: 288, + height: 580, borderRadius: 16, }); export const StyledTitle = styled(Typography)(({ theme }) => ({ color: theme.palette.primary.main, + display: "flex", + alignItems: "center", + gap: 8, +})); + +export const StyledTooltip = styled(Tooltip)(({ theme }) => ({ + color: theme.palette.grey[500], + "&:hover": { + color: theme.palette.primary.main, + }, })); export const StyledDescription = styled(Typography)(({ theme }) => ({ @@ -25,7 +34,6 @@ export const HeaderTableCell = styled(TableCell)(({ theme }) => ({ backgroundColor: theme.palette.common.white, borderBottomWidth: 1, zIndex: 0, - width: "30%", paddingLeft: 0, paddingBottom: 0, })); @@ -63,8 +71,8 @@ export const StatusContainer = styled("div")({ export const StyledStatusDot = styled("div", { shouldForwardProp: (prop) => prop !== "status", })<{ status?: string }>(({ status }) => ({ - width: 12, - height: 12, + width: 8, + height: 8, borderRadius: "50%", backgroundColor: getDeviceStatusColor(status), })); diff --git a/src/pages/StudyOverview/Overview/DeviceDeploymentStatus/index.tsx b/src/pages/StudyOverview/Overview/DeviceDeploymentStatus/index.tsx deleted file mode 100644 index 38cc9e9..0000000 --- a/src/pages/StudyOverview/Overview/DeviceDeploymentStatus/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import CarpErrorCardComponent from "@Components/CarpErrorCardComponent"; -import GeneratedAccountLabel from "@Components/GeneratedAccountLabel"; -import { useParticipantGroupsAccountsAndStatus } from "@Utils/queries/participants"; -import { DeviceStatus, ParticipantData } from "@carp-dk/client"; -import { - Table, - TableBody, - TableContainer, - TableHead, - Typography, -} from "@mui/material"; -import { useEffect, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import LoadingSkeleton from "../LoadingSkeleton"; -import { - HeaderTableCell, - HeaderText, - SecondaryCellText, - StatusContainer, - StyledCard, - StyledDescription, - StyledStatusDot, - StyledTableCell, - StyledTableRow, - StyledTitle, -} from "./styles"; - -const DeviceDeploymentStatus = () => { - const { id: studyId } = useParams(); - const navigate = useNavigate(); - const { - data: deploymentsAccountAndStatus, - isLoading: isDeploymentsAccountAndStatusLoading, - error: deploymentsAccountAndStatusError, - } = useParticipantGroupsAccountsAndStatus(studyId); - const [devicesNeededForRedeployment, setDevicesNeededForRedeployment] = - useState< - { - device: DeviceStatus; - type: string; - deploymentId: string; - participant: ParticipantData; - }[] - >([]); - - useEffect(() => { - if ( - deploymentsAccountAndStatus?.groups !== undefined && - deploymentsAccountAndStatus?.groups.length !== 0 - ) { - const devicesForRedeployment = deploymentsAccountAndStatus.groups - .map((g) => { - const devices = g.deploymentStatus.deviceStatusList.filter( - (dl) => - // eslint-disable-next-line no-underscore-dangle - dl.__type.split(".").pop() === "NeedsRedeployment" && - dl.device.isPrimaryDevice, - ); - const devicesWithParticipant = devices.map((d) => { - const { participantId } = - g.deploymentStatus.participantStatusList.find((psl) => - psl.assignedPrimaryDeviceRoleNames.includes(d.device.roleName), - ); - const participant = g.participants.find( - (p) => p.participantId === participantId, - ); - return { - device: d, - // eslint-disable-next-line no-underscore-dangle - type: d.__type.split(".").pop(), - deploymentId: g.participantGroupId, - participant, - }; - }); - return devicesWithParticipant; - }) - .flat(); - setDevicesNeededForRedeployment(devicesForRedeployment); - } - }, [deploymentsAccountAndStatus]); - - if (isDeploymentsAccountAndStatusLoading) { - return ; - } - - if (deploymentsAccountAndStatusError) { - return ( - - ); - } - - return ( - - Device deployment status - - List of primary devices the ones that are not successfully deployed. - - - - - - - Name - - - Email - - - Device Role - - - Device Status - - - - - {devicesNeededForRedeployment.map( - ({ device, type, deploymentId, participant }) => ( - - navigate( - `/studies/${studyId}/participants/deployments/${deploymentId}/participants/${participant.participantId}`, - ) - } - key={participant.participantId} - > - - - {`${participant.firstName ?? ""} ${ - participant.lastName ?? "" - }`} - - - - - {participant.email ?? } - - - - - {device.device.roleName} - - - - - - - {type} - - - - - ), - )} - -
-
-
- ); -}; - -export default DeviceDeploymentStatus; diff --git a/src/pages/StudyOverview/Overview/InactiveDeployments/index.tsx b/src/pages/StudyOverview/Overview/InactiveDeployments/index.tsx new file mode 100644 index 0000000..dd447c6 --- /dev/null +++ b/src/pages/StudyOverview/Overview/InactiveDeployments/index.tsx @@ -0,0 +1,141 @@ +import CarpErrorCardComponent from "@Components/CarpErrorCardComponent"; +import { useInactiveDeployments } from "@Utils/queries/participants"; +import { + MenuItem, + Table, + TableBody, + TableContainer, + TableHead, + Typography, +} from "@mui/material"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { Stack } from "@mui/system"; +import { formatDateTime } from "@Utils/utility"; +import LoadingSkeleton from "../LoadingSkeleton"; +import { + HeaderTableCell, + HeaderText, + SecondaryCellText, + StyledCard, + StyledDescription, + StyledSelect, + StyledTableCell, + StyledTableRow, + StyledTitle, +} from "./styles"; + +const InactiveDeployments = () => { + const { id: studyId } = useParams(); + const menuItems = [ + { value: 24, label: "24 h" }, + { value: 48, label: "48 h" }, + { value: 168, label: "1 week" }, + { value: 336, label: "2 weeks" }, + { value: 730, label: "1 month" }, + { value: 4380, label: "6 months" }, + ]; + + const [lastUpdateTime, setLastUpdateTime] = useState( + menuItems[0].value, + ); + + const { + data: inactiveDeployments, + isLoading: isInactiveDeploymentsLoading, + error: inactiveDeploymentsError, + } = useInactiveDeployments(studyId, lastUpdateTime); + + if (isInactiveDeploymentsLoading) { + return ; + } + + if (inactiveDeploymentsError) { + return ( + + ); + } + + return ( + + + Inactive Deployments + { + const selectedItem = menuItems.find( + (item) => item.value === lastUpdateTime, + ); + return {selectedItem.label}; + }} + onChange={(e) => { + setLastUpdateTime(e.target.value as unknown as number); + }} + > + {menuItems.map((item) => ( + + {item.label} + + ))} + + + + The following Deployments have not uploaded any data in the timeframe + selected. Select the Deployments ID for further information or to send a + reminder. + + + + + + + Deployment ID + + + Last Data + + + + + {inactiveDeployments.map((participant) => ( + + + + {participant.deploymentId as unknown as string} + + + + + {formatDateTime( + participant.dateOfLastDataUpload.toString(), + { year: "numeric", month: "numeric", day: "numeric" }, + )} + + + + ))} + +
+
+
+ ); +}; + +export default InactiveDeployments; diff --git a/src/pages/StudyOverview/Overview/InactiveParticipants/styles.ts b/src/pages/StudyOverview/Overview/InactiveDeployments/styles.ts similarity index 78% rename from src/pages/StudyOverview/Overview/InactiveParticipants/styles.ts rename to src/pages/StudyOverview/Overview/InactiveDeployments/styles.ts index fd6bd6f..3d7abf7 100644 --- a/src/pages/StudyOverview/Overview/InactiveParticipants/styles.ts +++ b/src/pages/StudyOverview/Overview/InactiveDeployments/styles.ts @@ -1,11 +1,11 @@ -import { Card, TableCell, TableRow, Typography } from "@mui/material"; +import { Card, Select, TableCell, TableRow, Typography } from "@mui/material"; import { styled } from "@Utils/theme"; export const StyledCard = styled(Card)({ display: "flex", flexDirection: "column", padding: 24, - height: 288, + height: 580, borderRadius: 16, }); @@ -23,7 +23,6 @@ export const HeaderTableCell = styled(TableCell)(({ theme }) => ({ backgroundColor: theme.palette.common.white, borderBottomWidth: 1, zIndex: 0, - width: "30%", paddingLeft: 0, paddingBottom: 0, })); @@ -51,3 +50,15 @@ export const StyledTableCell = styled(TableCell)({ paddingTop: "8px", border: "none", }); + +export const StyledSelect = styled(Select)({ + height: "32px", + width: "116px", + borderRadius: "16px", + "& .MuiOutlinedInput-notchedOutline": { + borderRadius: "16px", + }, + "& .MuiSelect-select": { + borderRadius: "16px", + }, +}); diff --git a/src/pages/StudyOverview/Overview/InactiveParticipants/index.tsx b/src/pages/StudyOverview/Overview/InactiveParticipants/index.tsx deleted file mode 100644 index bf08f58..0000000 --- a/src/pages/StudyOverview/Overview/InactiveParticipants/index.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import CarpErrorCardComponent from "@Components/CarpErrorCardComponent"; -import GeneratedAccountLabel from "@Components/GeneratedAccountLabel"; -import { useParticipantGroupsAccountsAndStatus } from "@Utils/queries/participants"; -import { formatDateTime } from "@Utils/utility"; -import { ParticipantData } from "@carp-dk/client"; -import { Table, TableBody, TableContainer, TableHead } from "@mui/material"; -import { useEffect, useMemo, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import LoadingSkeleton from "../LoadingSkeleton"; -import { - HeaderTableCell, - HeaderText, - SecondaryCellText, - StyledCard, - StyledDescription, - StyledTableCell, - StyledTableRow, - StyledTitle, -} from "./styles"; - -const InactiveParticipants = () => { - const { id: studyId } = useParams(); - const navigate = useNavigate(); - const { - data: deploymentsAccountAndStatus, - isLoading: isDeploymentsAccountAndStatusLoading, - error: deploymentsAccountAndStatusError, - } = useParticipantGroupsAccountsAndStatus(studyId); - const [inactiveParticipants, setInactiveParticipants] = useState< - (ParticipantData & { deploymentId: string })[] - >([]); - const [lastDataUploads, setLastDataUploads] = useState<{ - [key: string]: string; - }>({}); - - useEffect(() => { - if ( - deploymentsAccountAndStatus?.groups !== undefined && - deploymentsAccountAndStatus?.groups.length !== 0 - ) { - const participants = deploymentsAccountAndStatus.groups - .map((g) => { - const participantsWithDate = g.participants.filter( - (p) => p.dateOfLastDataUpload, - ); - return participantsWithDate.map((p) => ({ - ...p, - deploymentId: g.participantGroupId, - })); - }) - .flat() - .sort( - (a, b) => - new Date(b.dateOfLastDataUpload.value$kotlinx_datetime).getTime() - - new Date(a.dateOfLastDataUpload.value$kotlinx_datetime).getTime(), - ); - const uniqueParticipants = [...new Set(participants)].filter( - (p) => - new Date(p.dateOfLastDataUpload.value$kotlinx_datetime).getTime() <= - new Date().getTime() - 2 * 7 * 24 * 60 * 60 * 1000, - ); - setInactiveParticipants(uniqueParticipants); - } - }, [deploymentsAccountAndStatus]); - - useMemo(() => { - inactiveParticipants.forEach((p) => { - const lastDataUpload = p.dateOfLastDataUpload - ? formatDateTime( - p.dateOfLastDataUpload.value$kotlinx_datetime.toString(), - { - year: "numeric", - month: "numeric", - day: "numeric", - }, - ) - : ""; - setLastDataUploads((prevData) => ({ - ...prevData, - [p.participantId]: lastDataUpload, - })); - }); - }, [inactiveParticipants]); - - if (isDeploymentsAccountAndStatusLoading) { - return ; - } - - if (deploymentsAccountAndStatusError) { - return ( - - ); - } - - return ( - - Inactive Participants - - The following participants have not uploaded any data in the last 2 - weeks. Select the participant for further information or to send a - reminder. - - - - - - - Name - - - Email - - - Last Data - - - - - {inactiveParticipants.map((participant) => ( - - navigate( - `/studies/${studyId}/participants/deployments/${participant.deploymentId}/participants/${participant.participantId}`, - ) - } - key={participant.participantId} - > - - - {`${participant.firstName ?? ""} ${ - participant.lastName ?? "" - }`} - - - - - {participant.email ?? } - - - - - {lastDataUploads[participant.participantId]} - - - - ))} - -
-
-
- ); -}; - -export default InactiveParticipants; diff --git a/src/pages/StudyOverview/Overview/Participants/index.tsx b/src/pages/StudyOverview/Overview/Participants/index.tsx deleted file mode 100644 index c9932b9..0000000 --- a/src/pages/StudyOverview/Overview/Participants/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import CarpErrorCardComponent from "@Components/CarpErrorCardComponent"; -import { useParticipantsStatus } from "@Utils/queries/participants"; -import carpDeployments from "@cachet/carp-deployments-core"; -import carpStudies from "@cachet/carp-studies-core"; -import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; -import { Typography } from "@mui/material"; -import { useMemo } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import LoadingSkeleton from "../LoadingSkeleton"; -import { - ParticipantsRow, - StyledButton, - StyledCard, - StyledNumber, - StyledTitle, - Top, -} from "./styles"; - -import ParticipantGroupStatus = carpStudies.dk.cachet.carp.studies.application.users.ParticipantGroupStatus; - -const { DeviceDeploymentStatus } = - carpDeployments.dk.cachet.carp.deployments.application; - -const { PrimaryDeviceConfiguration } = - carpDeployments.dk.cachet.carp.common.application.devices; - -const Participants = () => { - const navigate = useNavigate(); - const { id: studyId } = useParams(); - const { - data: participantsStatus, - isLoading: participantsStatusLoading, - error: participantsStatusError, - } = useParticipantsStatus(studyId); - - const numberOfRegisteredDevices = useMemo(() => { - if (!participantsStatus) return 0; - return participantsStatus - .filter((ps) => ps instanceof ParticipantGroupStatus.InDeployment) - .map((ps: ParticipantGroupStatus.InDeployment) => { - return ps.studyDeploymentStatus.deviceStatusList - .toArray() - .filter( - (device) => - device instanceof DeviceDeploymentStatus.Registered && - device.device instanceof PrimaryDeviceConfiguration, - ).length; - }) - .reduce((a, b) => a + b, 0); - }, [participantsStatus]); - - const numberOfDeployedDevices = useMemo(() => { - if (!participantsStatus) return 0; - return participantsStatus - .filter((ps) => ps instanceof ParticipantGroupStatus.InDeployment) - .map((ps: ParticipantGroupStatus.InDeployment) => { - return ps.studyDeploymentStatus.deviceStatusList - .toArray() - .filter( - (device) => - device instanceof DeviceDeploymentStatus.Deployed && - device.device instanceof PrimaryDeviceConfiguration, - ).length; - }) - .reduce((a, b) => a + b, 0); - }, [participantsStatus]); - - const numberOfUnregisteredDevices = useMemo(() => { - if (!participantsStatus) return 0; - return participantsStatus - .filter((ps) => ps instanceof ParticipantGroupStatus.InDeployment) - .map((ps: ParticipantGroupStatus.InDeployment) => { - return ps.studyDeploymentStatus.deviceStatusList - .toArray() - .filter( - (device) => - device instanceof DeviceDeploymentStatus.Unregistered && - device.device instanceof PrimaryDeviceConfiguration, - ).length; - }) - .reduce((a, b) => a + b, 0); - }, [participantsStatus]); - - const numberOfNeedsRedeploymentDevices = useMemo(() => { - if (!participantsStatus) return 0; - return participantsStatus - .filter((ps) => ps instanceof ParticipantGroupStatus.InDeployment) - .map((ps: ParticipantGroupStatus.InDeployment) => { - return ps.studyDeploymentStatus.deviceStatusList - .toArray() - .filter( - (device) => - device instanceof DeviceDeploymentStatus.NeedsRedeployment && - device.device instanceof PrimaryDeviceConfiguration, - ).length; - }) - .reduce((a, b) => a + b, 0); - }, [participantsStatus]); - - const numberOfParticipants = useMemo(() => { - if (!participantsStatus) return 0; - return participantsStatus - .filter((ps) => ps instanceof ParticipantGroupStatus.InDeployment) - .map((ps: ParticipantGroupStatus.InDeployment) => { - return ps.studyDeploymentStatus.deviceStatusList - .toArray() - .filter((g) => g.device instanceof PrimaryDeviceConfiguration).length; - }) - .reduce((a, b) => a + b, 0); - }, [participantsStatus]); - - if (participantsStatusLoading) return ; - if (participantsStatusError) - return ( - - ); - - return ( - - - Participants - - navigate(`/studies/${studyId}/participants/deployments`) - } - variant="outlined" - > - - Manage - - - - - {numberOfParticipants} - - Participants - - - - {numberOfDeployedDevices} - - Deployed - - - - {numberOfRegisteredDevices} - - Registered - - - - {numberOfUnregisteredDevices} - - Unregistered - - - - {numberOfNeedsRedeploymentDevices} - - Needs Redeployment - - - ); -}; - -export default Participants; diff --git a/src/pages/StudyOverview/Overview/Status/index.tsx b/src/pages/StudyOverview/Overview/Status/index.tsx index 8a83320..4775cbf 100644 --- a/src/pages/StudyOverview/Overview/Status/index.tsx +++ b/src/pages/StudyOverview/Overview/Status/index.tsx @@ -4,6 +4,8 @@ import { formatDateTime } from "@Utils/utility"; import kotlinx from "@cachet/carp-kotlinx-datetime"; import carpStudies from "@cachet/carp-studies-core"; import { useNavigate, useParams } from "react-router-dom"; +import { Typography } from "@mui/material"; +import LinkIcon from "@mui/icons-material/Link"; import LoadingSkeleton from "../LoadingSkeleton"; import { ProtocolData, @@ -76,6 +78,9 @@ const Status = () => { Study Protocol: {studyDetails.protocolSnapshot.name} + + {studyDetails.protocolSnapshot.description} + { @@ -83,6 +88,12 @@ const Status = () => { }} > See detailed information in Study Settings + )} diff --git a/src/pages/StudyOverview/Overview/Status/styles.ts b/src/pages/StudyOverview/Overview/Status/styles.ts index 038d167..36a862c 100644 --- a/src/pages/StudyOverview/Overview/Status/styles.ts +++ b/src/pages/StudyOverview/Overview/Status/styles.ts @@ -31,10 +31,11 @@ export const Top = styled("div")(({ theme }) => ({ export const ProtocolData = styled(Typography)(({ theme }) => ({ color: theme.palette.text.primary, + marginBottom: 8, })); export const StyledLink = styled(Typography)(({ theme }) => ({ - marginTop: 16, + marginTop: 24, color: theme.palette.primary.main, cursor: "pointer", textDecoration: "underline", diff --git a/src/pages/StudyOverview/Overview/StudyData/index.tsx b/src/pages/StudyOverview/Overview/StudyData/index.tsx deleted file mode 100644 index 57c920c..0000000 --- a/src/pages/StudyOverview/Overview/StudyData/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StyledCard, StyledDescription, StyledTitle } from "./styles"; - -const StudyData = () => { - return ( - - Study Data - - See an overview of the collected and expected data in percentages, in - the last 2 weeks. - - - ); -}; - -export default StudyData; diff --git a/src/pages/StudyOverview/Overview/StudyData/styles.ts b/src/pages/StudyOverview/Overview/StudyData/styles.ts deleted file mode 100644 index 478ccc7..0000000 --- a/src/pages/StudyOverview/Overview/StudyData/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Card, Typography } from "@mui/material"; -import { styled } from "@Utils/theme"; - -export const StyledCard = styled(Card)({ - display: "flex", - flexDirection: "column", - gridColumn: "span 2", - padding: 24, - height: 288, - borderRadius: 16, -}); - -export const StyledTitle = styled(Typography)(({ theme }) => ({ - color: theme.palette.primary.main, -})); - -export const StyledDescription = styled(Typography)(({ theme }) => ({ - color: theme.palette.text.secondary, - marginTop: 8, -})); diff --git a/src/pages/StudyOverview/Overview/StudyDataTypes/index.tsx b/src/pages/StudyOverview/Overview/StudyDataTypes/index.tsx deleted file mode 100644 index 0ed2731..0000000 --- a/src/pages/StudyOverview/Overview/StudyDataTypes/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { StyledCard, StyledDescription, StyledTitle } from "./styles"; - -const StudyDataTypes = () => { - return ( - - Data types - - See an overview of the collected data in the last 2 weeks. - - - ); -}; - -export default StudyDataTypes; diff --git a/src/pages/StudyOverview/Overview/StudyDataTypes/styles.ts b/src/pages/StudyOverview/Overview/StudyDataTypes/styles.ts deleted file mode 100644 index ffdc68a..0000000 --- a/src/pages/StudyOverview/Overview/StudyDataTypes/styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Card, 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 StyledTitle = styled(Typography)(({ theme }) => ({ - color: theme.palette.primary.main, -})); - -export const StyledDescription = styled(Typography)(({ theme }) => ({ - color: theme.palette.text.secondary, - marginTop: 8, -})); diff --git a/src/pages/StudyOverview/Overview/index.tsx b/src/pages/StudyOverview/Overview/index.tsx index 95f1c16..53ff2ad 100644 --- a/src/pages/StudyOverview/Overview/index.tsx +++ b/src/pages/StudyOverview/Overview/index.tsx @@ -1,6 +1,6 @@ -import DeviceDeploymentStatus from "./DeviceDeploymentStatus"; -import InactiveParticipants from "./InactiveParticipants"; -import Participants from "./Participants"; +import DeploymentStatus from "./DeploymentStatus"; +import DeploymentsInProgress from "./DeploymentsInProgress"; +import InactiveDeployments from "./InactiveDeployments"; import Status from "./Status"; import StyledContainer from "./styles"; @@ -8,11 +8,9 @@ const Overview = () => { return ( - - {/* */} - - - {/* */} + + + ); }; diff --git a/src/pages/StudyOverview/Overview/styles.ts b/src/pages/StudyOverview/Overview/styles.ts index 0d4de86..2675953 100644 --- a/src/pages/StudyOverview/Overview/styles.ts +++ b/src/pages/StudyOverview/Overview/styles.ts @@ -2,7 +2,7 @@ import { styled } from "@Utils/theme"; const StyledContainer = styled("div")({ display: "grid", - gridTemplateColumns: "repeat(auto-fit, minmax(350px, 1fr))", + gridTemplateColumns: "repeat(auto-fill, minmax(450px, 1fr))", gap: 48, }); diff --git a/src/pages/StudySettings/StudyData/index.tsx b/src/pages/StudySettings/StudyData/index.tsx index a2deb85..c879aec 100644 --- a/src/pages/StudySettings/StudyData/index.tsx +++ b/src/pages/StudySettings/StudyData/index.tsx @@ -141,7 +141,7 @@ const StudyData = () => { value={studyProtocolFormik.values.protocolId} onChange={handleProtocolChange} > - {protocols.map((protocol) => ( + {protocols?.map((protocol) => ( { }); }; +export const useInactiveDeployments = (studyId: string, lastUpdate: number) => { + return useQuery({ + queryFn: () => + carpApi.getInactiveDeployments(studyId, lastUpdate, getConfig()), + queryKey: ["inactiveDeployments", { studyId, lastUpdate }], + }); +}; + export const useInviteParticipants = (studyId: string) => { const { setSnackbarSuccess, setSnackbarError } = useSnackbar(); const queryClient = useQueryClient(); diff --git a/src/utils/queries/studies.ts b/src/utils/queries/studies.ts index 58545b5..33c57ad 100644 --- a/src/utils/queries/studies.ts +++ b/src/utils/queries/studies.ts @@ -306,7 +306,7 @@ export const useRemoveResearcherFromStudy = (studyId: string) => { export const useDeleteStudy = () => { const { setSnackbarSuccess, setSnackbarError } = useSnackbar(); const queryClient = useQueryClient(); - let id = ''; + let id = ""; return useMutation({ mutationFn: async (studyId: string) => { @@ -314,11 +314,11 @@ export const useDeleteStudy = () => { return carpApi.deleteStudy_CORE(studyId, getConfig()); }, onSuccess: () => { - queryClient.setQueryData(['studies'], (old: StudyOverview[]) => - old.filter((study) => study.studyId !== id) + queryClient.setQueryData(["studies"], (old: StudyOverview[]) => + old.filter((study) => study.studyId !== id), ); - queryClient.invalidateQueries({ queryKey: ['studies'] }); - setSnackbarSuccess('Study deleted!'); + queryClient.invalidateQueries({ queryKey: ["studies"] }); + setSnackbarSuccess("Study deleted!"); }, onError: (error: CarpServiceError) => { setSnackbarError(error.httpResponseMessage); diff --git a/src/utils/theme.tsx b/src/utils/theme.tsx index e124e39..caed804 100644 --- a/src/utils/theme.tsx +++ b/src/utils/theme.tsx @@ -134,6 +134,7 @@ export const customPalette = { green: "#00A300", red: "#BA1A1A", grey: "#76777A", + blue: "#006398", }, drawer: { active: "#E7F2FF", diff --git a/src/utils/utility.tsx b/src/utils/utility.tsx index 7237fef..f8c2faf 100644 --- a/src/utils/utility.tsx +++ b/src/utils/utility.tsx @@ -223,6 +223,8 @@ export const getDeploymentStatusColor = (deploymentStatus: string) => { return palette.status.green; case "Stopped": return palette.status.grey; + case "Deploying": + return palette.status.blue; default: return "#000000"; }