1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: improved health chart tooltip (#6359)

This commit is contained in:
Tymoteusz Czech 2024-02-28 08:58:27 +01:00 committed by GitHub
parent b82a650dab
commit 96c86b221b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 489 additions and 354 deletions

View File

@ -178,7 +178,7 @@ export const ExecutiveDashboard: VFC = () => {
value={80}
healthy={4}
stale={1}
potenciallyStale={0}
potentiallyStale={0}
/>
</Widget>
<Widget title='Health per project' order={7} span={chartSpan}>

View File

@ -6,14 +6,14 @@ interface IHealthStatsProps {
value: number;
healthy: number;
stale: number;
potenciallyStale: number;
potentiallyStale: number;
}
export const HealthStats: VFC<IHealthStatsProps> = ({
value,
healthy,
stale,
potenciallyStale,
potentiallyStale,
}) => {
const { themeMode } = useThemeMode();
const isDark = themeMode === 'dark';
@ -116,12 +116,12 @@ export const HealthStats: VFC<IHealthStatsProps> = ({
<text
x={144}
y={216}
fill={theme.palette.charts.health.potenciallyStale}
fill={theme.palette.charts.health.potentiallyStale}
fontWeight={700}
fontSize={20}
textAnchor='middle'
>
{potenciallyStale || 0}
{potentiallyStale || 0}
</text>
<text
x={144}
@ -302,7 +302,7 @@ export const HealthStats: VFC<IHealthStatsProps> = ({
<stop
offset='1'
stopColor={
theme.palette.charts.health.gradientPotenciallyStale
theme.palette.charts.health.gradientPotentiallyStale
}
/>
</linearGradient>

View File

@ -0,0 +1,41 @@
import { VFC } from 'react';
import { Box, styled } from '@mui/material';
type DistributionLineTypes = 'default' | 'success' | 'warning' | 'error';
const StyledDistributionLine = styled(Box)<{
type: DistributionLineTypes;
size?: 'large' | 'small';
}>(({ theme, type, size = 'large' }) => {
const color: Record<DistributionLineTypes, string | undefined> = {
default: theme.palette.secondary.border,
success: theme.palette.success.border,
warning: theme.palette.warning.border,
error: theme.palette.error.border,
};
return {
borderRadius: theme.shape.borderRadius,
height: size === 'large' ? theme.spacing(2) : theme.spacing(1),
backgroundColor: color[type],
marginBottom: theme.spacing(0.5),
};
});
export const HorizontalDistributionChart: VFC<{
sections: Array<{ type: DistributionLineTypes; value: number }>;
size?: 'large' | 'small';
}> = ({ sections, size }) => (
<Box sx={(theme) => ({ display: 'flex', gap: theme.spacing(0.5) })}>
{sections.map((section, index) =>
section.value ? (
<StyledDistributionLine
type={section.type}
sx={{ width: `${section.value}%` }}
key={`${section.type}-${index}`}
size={size}
/>
) : null,
)}
</Box>
);

View File

@ -7,7 +7,7 @@ export type TooltipState = {
caretX: number;
caretY: number;
title: string;
align: 'left' | 'right';
align: 'left' | 'right' | 'center';
body: {
title: string;
color: string;
@ -40,22 +40,47 @@ const StyledLabelIcon = styled('span')(({ theme }) => ({
marginRight: theme.spacing(1),
}));
const offset = 16;
const getAlign = (align?: 'left' | 'right' | 'center') => {
if (align === 'left') {
return 'flex-start';
}
if (align === 'right') {
return 'flex-end';
}
return 'center';
};
const getLeftOffset = (caretX = 0, align?: 'left' | 'right' | 'center') => {
if (align === 'left') {
return caretX + offset;
}
if (align === 'right') {
return caretX - offset;
}
return caretX;
};
export const ChartTooltipContainer: FC<IChartTooltipProps> = ({
tooltip,
children,
}) => (
<Box
sx={(theme) => ({
top: tooltip?.caretY,
left: tooltip?.align === 'left' ? tooltip?.caretX + 20 : 0,
right:
tooltip?.align === 'right' ? tooltip?.caretX + 20 : undefined,
top: (tooltip?.caretY || 0) + offset,
left: getLeftOffset(tooltip?.caretX, tooltip?.align),
width: '1px',
position: 'absolute',
display: tooltip ? 'flex' : 'none',
pointerEvents: 'none',
zIndex: theme.zIndex.tooltip,
flexDirection: 'column',
alignItems: tooltip?.align === 'left' ? 'flex-start' : 'flex-end',
alignItems: getAlign(tooltip?.align),
})}
>
{children}

View File

@ -10,16 +10,12 @@ import {
Chart,
Filler,
type ChartData,
TooltipModel,
ChartOptions,
type ChartOptions,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Theme, useTheme } from '@mui/material';
import {
useLocationSettings,
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { useTheme } from '@mui/material';
import { useLocationSettings } from 'hooks/useLocationSettings';
import {
ChartTooltip,
ChartTooltipContainer,
@ -27,144 +23,7 @@ import {
} from './ChartTooltip/ChartTooltip';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { styled } from '@mui/material';
const createTooltip =
(setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>) =>
(context: {
chart: Chart;
tooltip: TooltipModel<any>;
}) => {
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,
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
isPlaceholder?: boolean,
localTooltip?: boolean,
) =>
({
responsive: true,
...(isPlaceholder
? {
animation: {
duration: 0,
},
}
: {}),
plugins: {
legend: {
display: !isPlaceholder,
position: 'bottom',
labels: {
boxWidth: 12,
padding: 30,
generateLabels: (chart: Chart) => {
const datasets = chart.data.datasets;
const {
labels: {
usePointStyle,
pointStyle,
textAlign,
color,
},
} = chart?.legend?.options || {
labels: {},
};
return (chart as any)
._getSortedDatasetMetas()
.map((meta: any) => {
const style = meta.controller.getStyle(
usePointStyle ? 0 : undefined,
);
return {
text: datasets[meta.index].label,
fillStyle: style.backgroundColor,
fontColor: color,
hidden: !meta.visible,
lineWidth: 0,
borderRadius: 6,
strokeStyle: style.borderColor,
pointStyle: pointStyle || style.pointStyle,
textAlign: textAlign || style.textAlign,
datasetIndex: meta.index,
};
});
},
},
},
tooltip: {
enabled: false,
external: createTooltip(setTooltip),
},
},
locale: locationSettings.locale,
interaction: {
intersect: localTooltip || false,
axis: 'x',
},
elements: {
point: {
radius: 0,
hitRadius: 15,
},
},
// cubicInterpolationMode: 'monotone',
tension: 0.1,
color: theme.palette.text.secondary,
scales: {
y: {
beginAtZero: true,
type: 'linear',
grid: {
color: theme.palette.divider,
borderColor: theme.palette.divider,
},
ticks: {
color: theme.palette.text.secondary,
display: !isPlaceholder,
precision: 0,
},
},
x: {
type: 'time',
time: {
unit: 'day',
tooltipFormat: 'PPP',
},
grid: {
color: 'transparent',
borderColor: 'transparent',
},
ticks: {
color: theme.palette.text.secondary,
display: !isPlaceholder,
},
},
},
}) as const;
import { createOptions } from './createChartOptions';
const StyledContainer = styled('div')(({ theme }) => ({
position: 'relative',

View File

@ -0,0 +1,77 @@
import { Theme } from '@mui/material';
import { ILocationSettings } from 'hooks/useLocationSettings';
import { TooltipState } from './ChartTooltip/ChartTooltip';
import { createTooltip } from './createTooltip';
import { legendOptions } from './legendOptions';
export const createOptions = (
theme: Theme,
locationSettings: ILocationSettings,
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
isPlaceholder?: boolean,
localTooltip?: boolean,
) =>
({
responsive: true,
...(isPlaceholder
? {
animation: {
duration: 0,
},
}
: {}),
plugins: {
legend: {
...legendOptions,
display: !isPlaceholder,
},
tooltip: {
enabled: false,
external: createTooltip(setTooltip),
},
},
locale: locationSettings.locale,
interaction: {
intersect: localTooltip || false,
axis: 'x',
},
elements: {
point: {
radius: 0,
hitRadius: 15,
},
},
// cubicInterpolationMode: 'monotone',
tension: 0.1,
color: theme.palette.text.secondary,
scales: {
y: {
beginAtZero: true,
type: 'linear',
grid: {
color: theme.palette.divider,
borderColor: theme.palette.divider,
},
ticks: {
color: theme.palette.text.secondary,
display: !isPlaceholder,
precision: 0,
},
},
x: {
type: 'time',
time: {
unit: 'day',
tooltipFormat: 'PPP',
},
grid: {
color: 'transparent',
borderColor: 'transparent',
},
ticks: {
color: theme.palette.text.secondary,
display: !isPlaceholder,
},
},
},
}) as const;

View File

@ -0,0 +1,29 @@
import { type Chart, type TooltipModel } from 'chart.js';
import { type TooltipState } from './ChartTooltip/ChartTooltip';
export const createTooltip =
(setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>) =>
(context: {
chart: Chart;
tooltip: TooltipModel<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,
body:
tooltip?.body?.map((item: any, index: number) => ({
title: item?.lines?.join(' '),
color: tooltip?.labelColors?.[index]?.borderColor as string,
value: '',
})) || [],
dataPoints: tooltip?.dataPoints || [],
});
};

View File

@ -0,0 +1,34 @@
import { Chart } from 'chart.js';
export const legendOptions = {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 30,
generateLabels: (chart: Chart) => {
const datasets = chart.data.datasets;
const {
labels: { usePointStyle, pointStyle, textAlign, color },
} = chart?.legend?.options || {
labels: {},
};
return (chart as any)._getSortedDatasetMetas().map((meta: any) => {
const style = meta.controller.getStyle(
usePointStyle ? 0 : undefined,
);
return {
text: datasets[meta.index].label,
fillStyle: style.backgroundColor,
fontColor: color,
hidden: !meta.visible,
lineWidth: 0,
borderRadius: 6,
strokeStyle: style.borderColor,
pointStyle: pointStyle || style.pointStyle,
textAlign: textAlign || style.textAlign,
datasetIndex: meta.index,
};
});
},
},
} as const;

View File

@ -0,0 +1,164 @@
import { type VFC } from 'react';
import { type ExecutiveSummarySchemaProjectFlagTrendsItem } from 'openapi';
import { Box, Divider, Paper, Typography, styled } from '@mui/material';
import { Badge } from 'component/common/Badge/Badge';
import { TooltipState } from '../../LineChart/ChartTooltip/ChartTooltip';
import { HorizontalDistributionChart } from '../../HorizontalDistributionChart/HorizontalDistributionChart';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
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 Distribution = ({ stale = 0, potentiallyStale = 0, total = 0 }) => (
<>
<HorizontalDistributionChart
sections={[{ type: 'default', value: 100 }]}
size='small'
/>
<HorizontalDistributionChart
sections={[
{
type: 'error',
value: (stale / total) * 100,
},
{
type: 'warning',
value: (potentiallyStale / total) * 100,
},
]}
size='small'
/>
<Typography
variant='body2'
component='p'
sx={(theme) => ({ marginTop: theme.spacing(0.5) })}
>
<Typography
component='span'
sx={(theme) => ({
color: theme.palette.error.border,
})}
>
{'● '}
</Typography>
Stale flags: {stale}
</Typography>
<Typography variant='body2' component='p'>
<Typography
component='span'
sx={(theme) => ({
color: theme.palette.warning.border,
})}
>
{'● '}
</Typography>
Potentially stale flags: {potentiallyStale}
</Typography>
</>
);
export const HealthTooltip: VFC<{ tooltip: TooltipState | null }> = ({
tooltip,
}) => {
const data = tooltip?.dataPoints.map((point) => {
return {
label: point.label,
title: point.dataset.label,
color: point.dataset.borderColor,
value: point.raw as ExecutiveSummarySchemaProjectFlagTrendsItem,
};
});
const limitedData = data?.slice(0, 5);
return (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
width: '300px',
})}
>
{limitedData?.map((point, index) => (
<StyledTooltipItemContainer
elevation={3}
key={`${point.title}-${index}`}
>
<StyledItemHeader>
<Typography
variant='body2'
color='textSecondary'
component='span'
>
{point.label}
</Typography>
<Typography
variant='body2'
color='textSecondary'
component='span'
>
Project health
</Typography>
</StyledItemHeader>
<StyledItemHeader>
<Typography variant='body2' component='span'>
<Typography
sx={{ color: point.color }}
component='span'
>
{'● '}
</Typography>
<strong>{point.title}</strong>
</Typography>
<Badge color={getHealthBadgeColor(point.value.health)}>
{point.value.health}%
</Badge>
</StyledItemHeader>{' '}
<Divider
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
/>
<Typography
variant='body2'
component='p'
sx={(theme) => ({
marginBottom: theme.spacing(0.5),
})}
>
Total flags: {point.value.total}
</Typography>
<ConditionallyRender
condition={Boolean(
point.value.stale || point.value.potentiallyStale,
)}
show={<Distribution {...point.value} />}
/>
</StyledTooltipItemContainer>
)) || null}
</Box>
);
};

View File

@ -1,76 +1,14 @@
import { type VFC } from 'react';
import 'chartjs-adapter-date-fns';
import { ExecutiveSummarySchema } from 'openapi';
import { type VFC } from 'react';
import { type ExecutiveSummarySchema } from 'openapi';
import { HealthTooltip } from './HealthChartTooltip/HealthChartTooltip';
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 (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(3),
})}
>
{data?.map((point, index) => (
<StyledTooltipItemContainer elevation={3} key={point.title}>
<StyledItemHeader>
<div>{point.title}</div>{' '}
<Badge color={getHealthBadgeColor(point.value)}>
{point.value}%
</Badge>
</StyledItemHeader>
</StyledTooltipItemContainer>
)) || null}
</Box>
);
};
export const ProjectHealthChart: VFC<IFlagsProjectChartProps> = ({
projectFlagTrends,
}) => {
@ -80,12 +18,9 @@ export const ProjectHealthChart: VFC<IFlagsProjectChartProps> = ({
<LineChart
data={data}
isLocalTooltip
TooltipComponent={TooltipComponent}
TooltipComponent={HealthTooltip}
overrideOptions={{
parsing: {
yAxisKey: 'health',
xAxisKey: 'date',
},
parsing: { yAxisKey: 'health', xAxisKey: 'date' },
}}
/>
);

View File

@ -0,0 +1,78 @@
import React from 'react';
import { Box, Typography, styled } from '@mui/material';
type UserType = 'active' | 'inactive';
interface StyledLinearProgressProps {
type: UserType;
}
const StyledUserDistContainer = styled(Box)(({ theme }) => ({
padding: `${theme.spacing(1.5)} ${theme.spacing(2)}`,
borderRadius: `${theme.shape.borderRadius}px`,
border: `1px solid ${theme.palette.divider}`,
}));
const StyledUserDistIndicator = styled(Box)<StyledLinearProgressProps>(
({ theme, type }) => ({
width: 8,
height: 8,
backgroundColor:
type === 'active'
? theme.palette.success.border
: theme.palette.warning.border,
borderRadius: `2px`,
marginRight: theme.spacing(1),
}),
);
interface IUserDistributionInfoProps {
type: UserType;
count: string;
percentage: string;
}
const StyledDistInfoInnerContainer = styled(Box)(() => ({
display: 'flex',
alignItems: 'center',
width: '100%',
}));
const StyledDistInfoTextContainer = styled(Box)(() => ({
display: 'flex',
flexDirection: 'column',
}));
const StyledCountTypography = styled(Typography)(() => ({
marginLeft: 'auto',
fontWeight: 'normal',
}));
export const UserDistributionInfo: React.FC<IUserDistributionInfoProps> = ({
type,
count,
percentage,
}) => {
return (
<StyledUserDistContainer>
<StyledDistInfoInnerContainer>
<StyledDistInfoTextContainer>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<StyledUserDistIndicator type={type} />
<Typography variant='body1' component='span'>
{type === 'active' ? 'Active' : 'Inactive'} users
</Typography>
</Box>
<Typography variant='body2'>{percentage}%</Typography>
</StyledDistInfoTextContainer>
<StyledCountTypography variant='h2'>
{count}
</StyledCountTypography>
</StyledDistInfoInnerContainer>
</StyledUserDistContainer>
);
};

View File

@ -1,9 +1,11 @@
import React, { type FC } from 'react';
import { type FC } from 'react';
import { ChevronRight } from '@mui/icons-material';
import { Box, Typography, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUiFlag } from 'hooks/useUiFlag';
import { Link } from 'react-router-dom';
import { HorizontalDistributionChart } from '../HorizontalDistributionChart/HorizontalDistributionChart';
import { UserDistributionInfo } from './UserDistributionInfo';
const StyledUserContainer = styled(Box)(({ theme }) => ({
position: 'relative',
@ -43,12 +45,6 @@ const StyledUserCount = styled(Typography)(({ theme }) => ({
padding: 0,
}));
const StyledHeader = styled(Typography)(({ theme }) => ({
marginBottom: theme.spacing(3),
fontSize: theme.fontSizes.bodySize,
fontWeight: 'bold',
}));
const StyledDistInfoContainer = styled(Box)({
display: 'flex',
flexDirection: 'column',
@ -95,8 +91,17 @@ export const UserStats: FC<IUserStatsProps> = ({ count, active, inactive }) => {
show={
<>
<StyledUserDistributionContainer>
<UserDistribution
activeUsersPercentage={activeUsersPercentage}
<HorizontalDistributionChart
sections={[
{
type: 'success',
value: activeUsersPercentage,
},
{
type: 'warning',
value: 100 - activeUsersPercentage,
},
]}
/>
</StyledUserDistributionContainer>
@ -124,115 +129,3 @@ export const UserStats: FC<IUserStatsProps> = ({ count, active, inactive }) => {
</>
);
};
type UserType = 'active' | 'inactive';
interface StyledLinearProgressProps {
type: UserType;
}
const StyledUserDistributionLine = styled(Box)<StyledLinearProgressProps>(
({ theme, type }) => ({
borderRadius: theme.shape.borderRadius,
height: 16,
backgroundColor:
type === 'active'
? theme.palette.success.border
: theme.palette.warning.border,
}),
);
const UserDistribution = ({ activeUsersPercentage = 100 }) => {
const getLineWidth = () => {
return [activeUsersPercentage, 100 - activeUsersPercentage];
};
const [activeWidth, inactiveWidth] = getLineWidth();
return (
<Box sx={{ display: 'flex' }}>
<StyledUserDistributionLine
type='active'
sx={{ width: `${activeWidth}%` }}
/>
<StyledUserDistributionLine
type='inactive'
sx={(theme) => ({
width: `${inactiveWidth}%`,
marginLeft: theme.spacing(0.5),
})}
/>
</Box>
);
};
const StyledUserDistContainer = styled(Box)(({ theme }) => ({
padding: `${theme.spacing(1.5)} ${theme.spacing(2)}`,
borderRadius: `${theme.shape.borderRadius}px`,
border: `1px solid ${theme.palette.divider}`,
}));
const StyledUserDistIndicator = styled(Box)<StyledLinearProgressProps>(
({ theme, type }) => ({
width: 8,
height: 8,
backgroundColor:
type === 'active'
? theme.palette.success.border
: theme.palette.warning.border,
borderRadius: `2px`,
marginRight: theme.spacing(1),
}),
);
interface IUserDistributionInfoProps {
type: UserType;
count: string;
percentage: string;
}
const StyledDistInfoInnerContainer = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
width: '100%',
}));
const StyledDistInfoTextContainer = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
}));
const StyledCountTypography = styled(Typography)(({ theme }) => ({
marginLeft: 'auto',
fontWeight: 'normal',
}));
const UserDistributionInfo: React.FC<IUserDistributionInfoProps> = ({
type,
count,
percentage,
}) => {
return (
<StyledUserDistContainer>
<StyledDistInfoInnerContainer>
<StyledDistInfoTextContainer>
<Box
sx={{
display: 'flex',
alignItems: 'center',
}}
>
<StyledUserDistIndicator type={type} />
<Typography variant='body1' component='span'>
{type === 'active' ? 'Active' : 'Inactive'} users
</Typography>
</Box>
<Typography variant='body2'>{percentage}%</Typography>
</StyledDistInfoTextContainer>
<StyledCountTypography variant='h2'>
{count}
</StyledCountTypography>
</StyledDistInfoInnerContainer>
</StyledUserDistContainer>
);
};

View File

@ -298,9 +298,9 @@ const theme = {
title: colors.grey[50],
healthy: colors.purple[800],
stale: colors.red[800],
potenciallyStale: colors.orange[800],
potentiallyStale: colors.orange[800],
gradientStale: '#8A3E45',
gradientPotenciallyStale: '#875D21',
gradientPotentiallyStale: '#875D21',
},
},
},

View File

@ -283,9 +283,9 @@ export const theme = {
title: colors.grey[50],
healthy: colors.purple[800],
stale: colors.red[800],
potenciallyStale: colors.orange[800],
potentiallyStale: colors.orange[800],
gradientStale: colors.red[300],
gradientPotenciallyStale: colors.orange[500],
gradientPotentiallyStale: colors.orange[500],
},
},
},

View File

@ -141,9 +141,9 @@ declare module '@mui/material/styles' {
title: string;
healthy: string;
stale: string;
potenciallyStale: string;
potentiallyStale: string;
gradientStale: string;
gradientPotenciallyStale: string;
gradientPotentiallyStale: string;
};
};
}