diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index 05f36cef136..1baee2436f7 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -77,7 +77,7 @@ export const ExecutiveDashboard: VFC = () => { ({ paddingBottom: theme.spacing(4) })}> + Dashboard } diff --git a/frontend/src/component/executiveDashboard/UsersChart/ChartTooltip/ChartTooltip.tsx b/frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx similarity index 100% rename from frontend/src/component/executiveDashboard/UsersChart/ChartTooltip/ChartTooltip.tsx rename to frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx diff --git a/frontend/src/component/executiveDashboard/LineChart/LineChart.tsx b/frontend/src/component/executiveDashboard/LineChart/LineChart.tsx new file mode 100644 index 00000000000..780ef86688c --- /dev/null +++ b/frontend/src/component/executiveDashboard/LineChart/LineChart.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const LineChart = lazy(() => import('./LineChartComponent')); diff --git a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx b/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx similarity index 76% rename from frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx rename to frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx index e1b4de2daf4..7932d113405 100644 --- a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx @@ -1,15 +1,15 @@ import { useMemo, useState, type VFC } from 'react'; import { - Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, - Title, Tooltip, Legend, TimeScale, Chart, + type ChartData, + type ScatterDataPoint, } from 'chart.js'; import { Line } from 'react-chartjs-2'; import 'chartjs-adapter-date-fns'; @@ -18,7 +18,6 @@ import { useLocationSettings, type ILocationSettings, } from 'hooks/useLocationSettings'; -import { ExecutiveSummarySchema } from 'openapi'; import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip'; const createOptions = ( @@ -34,7 +33,6 @@ const createOptions = ( labels: { boxWidth: 12, padding: 30, - // usePointStyle: true, generateLabels: (chart: Chart) => { const datasets = chart.data.datasets; const { @@ -122,8 +120,8 @@ const createOptions = ( unit: 'month', }, grid: { - color: theme.palette.divider, - borderColor: theme.palette.divider, + color: 'transparent', + borderColor: 'transparent', }, ticks: { color: theme.palette.text.secondary, @@ -132,47 +130,41 @@ const createOptions = ( }, }) as const; -interface IUsersChartComponentProps { - userTrends: ExecutiveSummarySchema['userTrends']; -} - -const createData = ( - theme: Theme, - userTrends: ExecutiveSummarySchema['userTrends'], -) => ({ - labels: userTrends.map((item) => item.date), - datasets: [ - { - label: 'Total users', - data: userTrends.map((item) => item.total), - borderColor: theme.palette.primary.light, - backgroundColor: theme.palette.primary.light, - fill: true, - }, - { - label: 'Active users', - data: userTrends.map((item) => item.active), - borderColor: theme.palette.success.border, - backgroundColor: theme.palette.success.border, - }, - { - label: 'Inactive users', - data: userTrends.map((item) => item.inactive), - borderColor: theme.palette.warning.border, - backgroundColor: theme.palette.warning.border, - }, - ], -}); +// Vertical line on the hovered chart, filled with gradient. Highlights a section of a chart when you hover over datapoints +const customHighlightPlugin = { + id: 'customLine', + afterDraw: (chart: Chart) => { + const width = 26; + if (chart.tooltip?.opacity && chart.tooltip.x) { + const x = chart.tooltip.caretX; + const yAxis = chart.scales.y; + const ctx = chart.ctx; + ctx.save(); + const gradient = ctx.createLinearGradient( + x, + yAxis.top, + x, + yAxis.bottom, + ); + gradient.addColorStop(0, 'rgba(129, 122, 254, 0)'); + gradient.addColorStop(1, 'rgba(129, 122, 254, 0.12)'); + ctx.fillStyle = gradient; + ctx.fillRect( + x - width / 2, + yAxis.top, + width, + yAxis.bottom - yAxis.top, + ); + ctx.restore(); + } + }, +}; -const UsersChartComponent: VFC = ({ - userTrends, -}) => { +const LineChartComponent: VFC<{ + data: ChartData<'line', (number | ScatterDataPoint | null)[], unknown>; +}> = ({ data }) => { const theme = useTheme(); const { locationSettings } = useLocationSettings(); - const data = useMemo( - () => createData(theme, userTrends), - [theme, userTrends], - ); const [tooltip, setTooltip] = useState(null); const options = useMemo( @@ -182,21 +174,25 @@ const UsersChartComponent: VFC = ({ return ( <> - + ); }; -ChartJS.register( +Chart.register( CategoryScale, LinearScale, PointElement, LineElement, TimeScale, - Title, Tooltip, Legend, ); -export default UsersChartComponent; +// for lazy-loading +export default LineChartComponent; diff --git a/frontend/src/component/executiveDashboard/UsersChart/UsersChart.tsx b/frontend/src/component/executiveDashboard/UsersChart/UsersChart.tsx index ee5a468d680..f1786d23c5a 100644 --- a/frontend/src/component/executiveDashboard/UsersChart/UsersChart.tsx +++ b/frontend/src/component/executiveDashboard/UsersChart/UsersChart.tsx @@ -1,3 +1,42 @@ -import { lazy } from 'react'; +import { useMemo, type VFC } from 'react'; +import 'chartjs-adapter-date-fns'; +import { useTheme } from '@mui/material'; +import { ExecutiveSummarySchema } from 'openapi'; +import { LineChart } from '../LineChart/LineChart'; -export const UsersChart = lazy(() => import('./UsersChartComponent')); +interface IUsersChartProps { + userTrends: ExecutiveSummarySchema['userTrends']; +} + +export const UsersChart: VFC = ({ userTrends }) => { + const theme = useTheme(); + const data = useMemo( + () => ({ + labels: userTrends.map((item) => item.date), + datasets: [ + { + label: 'Total users', + data: userTrends.map((item) => item.total), + borderColor: theme.palette.primary.light, + backgroundColor: theme.palette.primary.light, + fill: true, + }, + { + label: 'Active users', + data: userTrends.map((item) => item.active), + borderColor: theme.palette.success.border, + backgroundColor: theme.palette.success.border, + }, + { + label: 'Inactive users', + data: userTrends.map((item) => item.inactive), + borderColor: theme.palette.warning.border, + backgroundColor: theme.palette.warning.border, + }, + ], + }), + [theme, userTrends], + ); + + return ; +}; diff --git a/frontend/src/component/executiveDashboard/Widget/Widget.tsx b/frontend/src/component/executiveDashboard/Widget/Widget.tsx index 10367590511..d39c8a9c54c 100644 --- a/frontend/src/component/executiveDashboard/Widget/Widget.tsx +++ b/frontend/src/component/executiveDashboard/Widget/Widget.tsx @@ -1,23 +1,27 @@ import { FC, ReactNode } from 'react'; -import { Paper, Typography } from '@mui/material'; +import { Paper, Typography, styled } from '@mui/material'; import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +const StyledPaper = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(3), + borderRadius: `${theme.shape.borderRadiusLarge}px`, + minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128 + position: 'relative', +})); + export const Widget: FC<{ title: ReactNode; order?: number; span?: number; tooltip?: ReactNode; }> = ({ title, order, children, span = 1, tooltip }) => ( - ({ - padding: 3, - borderRadius: `${theme.shape.borderRadiusLarge}px`, + sx={{ order, gridColumn: `span ${span}`, - minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128 - })} + }} > {children} - + );