diff --git a/frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx b/frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx index 4a48c5ac2617..b189c7874767 100644 --- a/frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx +++ b/frontend/src/component/executiveDashboard/LineChart/ChartTooltip/ChartTooltip.tsx @@ -1,5 +1,6 @@ -import { Paper, styled, Typography } from '@mui/material'; -import { VFC } from 'react'; +import { Box, Paper, styled, Typography } from '@mui/material'; +import { TooltipItem } from 'chart.js'; +import { FC, VFC } from 'react'; import { objectId } from 'utils/objectId'; export type TooltipState = { @@ -12,6 +13,7 @@ export type TooltipState = { color: string; value: string; }[]; + dataPoints: TooltipItem[]; }; interface IChartTooltipProps { @@ -38,54 +40,69 @@ const StyledLabelIcon = styled('span')(({ theme }) => ({ marginRight: theme.spacing(1), })); -export const ChartTooltip: VFC = ({ tooltip }) => ( - = ({ + tooltip, + children, +}) => ( + ({ top: tooltip?.caretY, - left: - tooltip?.align === 'left' - ? tooltip?.caretX + 40 - : (tooltip?.caretX || 0) - 220, + left: tooltip?.align === 'left' ? tooltip?.caretX + 20 : 0, + right: + tooltip?.align === 'right' ? tooltip?.caretX + 20 : undefined, position: 'absolute', - display: tooltip ? 'block' : 'none', - width: 220, - padding: theme.spacing(1.5, 2), + display: tooltip ? 'flex' : 'none', pointerEvents: 'none', zIndex: theme.zIndex.tooltip, + flexDirection: 'column', + alignItems: tooltip?.align === 'left' ? 'flex-start' : 'flex-end', })} > - { - ({ - marginBottom: theme.spacing(1), - color: theme.palette.text.secondary, - })} - > - {tooltip?.title} - - } - - {tooltip?.body.map((item) => ( - - - {' '} - - - {item.title} - - - ))} - - + {children} + +); + +export const ChartTooltip: VFC = ({ tooltip }) => ( + + ({ + width: 220, + padding: theme.spacing(1.5, 2), + })} + > + { + ({ + marginBottom: theme.spacing(1), + color: theme.palette.text.secondary, + })} + > + {tooltip?.title} + + } + + {tooltip?.body.map((item) => ( + + + {' '} + + + {item.title} + + + ))} + + + ); diff --git a/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx b/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx index 34b2bec05084..5f606400ccec 100644 --- a/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/LineChart/LineChartComponent.tsx @@ -11,6 +11,7 @@ import { Filler, type ChartData, type ScatterDataPoint, + TooltipModel, } from 'chart.js'; import { Line } from 'react-chartjs-2'; import 'chartjs-adapter-date-fns'; @@ -19,10 +20,44 @@ import { useLocationSettings, type ILocationSettings, } from 'hooks/useLocationSettings'; -import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip'; +import { + ChartTooltip, + ChartTooltipContainer, + TooltipState, +} from './ChartTooltip/ChartTooltip'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { styled } from '@mui/material'; +const createTooltip = + (setTooltip: React.Dispatch>) => + (context: { + chart: Chart; + tooltip: TooltipModel; + }) => { + const tooltip = context.tooltip; + if (tooltip.opacity === 0) { + setTooltip(null); + return; + } + + setTooltip({ + caretX: + tooltip?.xAlign === 'right' + ? context.chart.width - tooltip?.caretX + : tooltip?.caretX, + caretY: tooltip?.caretY, + title: tooltip?.title?.join(' ') || '', + align: tooltip?.xAlign === 'right' ? 'right' : 'left', + body: + tooltip?.body?.map((item: any, index: number) => ({ + title: item?.lines?.join(' '), + color: tooltip?.labelColors?.[index]?.borderColor as string, + value: '', + })) || [], + dataPoints: tooltip?.dataPoints || [], + }); + }; + const createOptions = ( theme: Theme, locationSettings: ILocationSettings, @@ -82,26 +117,7 @@ const createOptions = ( }, tooltip: { enabled: false, - external: (context: any) => { - const tooltip = context.tooltip; - if (tooltip.opacity === 0) { - setTooltip(null); - return; - } - - setTooltip({ - caretX: tooltip?.caretX, - caretY: tooltip?.caretY, - title: tooltip?.title?.join(' ') || '', - align: tooltip?.xAlign || 'left', - body: - tooltip?.body?.map((item: any, index: number) => ({ - title: item?.lines?.join(' '), - color: tooltip?.labelColors?.[index] - ?.borderColor, - })) || [], - }); - }, + external: createTooltip(setTooltip), }, }, locale: locationSettings.locale, @@ -211,7 +227,10 @@ const LineChartComponent: VFC<{ aspectRatio?: number; cover?: ReactNode; isLocalTooltip?: boolean; -}> = ({ data, aspectRatio, cover, isLocalTooltip }) => { + TooltipComponent?: ({ + tooltip, + }: { tooltip: TooltipState | null }) => ReturnType; +}> = ({ data, aspectRatio, cover, isLocalTooltip, TooltipComponent }) => { const theme = useTheme(); const { locationSettings } = useLocationSettings(); @@ -239,7 +258,15 @@ const LineChartComponent: VFC<{ /> } + show={ + TooltipComponent ? ( + + + + ) : ( + + ) + } elseShow={ diff --git a/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx b/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx index f436fa836d7a..2666f57935ac 100644 --- a/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx +++ b/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx @@ -3,15 +3,84 @@ import 'chartjs-adapter-date-fns'; import { ExecutiveSummarySchema } from 'openapi'; import { LineChart } from '../LineChart/LineChart'; import { useProjectChartData } from '../useProjectChartData'; +import { TooltipState } from '../LineChart/ChartTooltip/ChartTooltip'; +import { Box, Paper, styled } from '@mui/material'; +import { Badge } from 'component/common/Badge/Badge'; interface IFlagsProjectChartProps { projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends']; } +const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(2), +})); + +const StyledItemHeader = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + gap: theme.spacing(2), + alignItems: 'center', +})); + +const getHealthBadgeColor = (health?: number | null) => { + if (health === undefined || health === null) { + return 'info'; + } + + if (health >= 75) { + return 'success'; + } + + if (health >= 50) { + return 'warning'; + } + + return 'error'; +}; + +const TooltipComponent: VFC<{ tooltip: TooltipState | null }> = ({ + tooltip, +}) => { + const data = tooltip?.dataPoints.map((point) => { + return { + title: point.dataset.label, + color: point.dataset.borderColor, + value: point.raw as number, + }; + }); + + return ( + ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(3), + })} + > + {data?.map((point, index) => ( + + +
{point.title}
{' '} + + {point.value}% + +
+
+ )) || null} +
+ ); +}; + export const ProjectHealthChart: VFC = ({ projectFlagTrends, }) => { const data = useProjectChartData(projectFlagTrends, 'health'); - return ; + return ( + + ); };