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

feat: executive dashboard responsive grid (#6069)

- unified "Widget" component
- column order dependent on screen width
This commit is contained in:
Tymoteusz Czech 2024-01-30 17:02:13 +01:00 committed by GitHub
parent ccc41dca4e
commit c9ac4916e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 148 additions and 106 deletions

View File

@ -1,24 +1,61 @@
import { Box, Paper, styled, Typography } from '@mui/material';
import { useMemo, VFC } from 'react';
import {
Box,
styled,
Typography,
useMediaQuery,
useTheme,
} from '@mui/material';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { VFC } from 'react';
import { UsersChart } from './UsersChart/UsersChart';
import { FlagsChart } from './FlagsChart/FlagsChart';
import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary';
import { UserStats } from './UserStats/UserStats';
import { FlagStats } from './FlagStats/FlagStats';
import { Widget } from './Widget/Widget';
const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
gridTemplateColumns: `300px 1fr`,
// TODO: responsive grid size
gridAutoRows: 'auto',
gap: theme.spacing(2),
}));
const useDashboardGrid = () => {
const theme = useTheme();
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
if (isSmallScreen) {
return {
gridTemplateColumns: `1fr`,
chartSpan: 1,
userTrendsOrder: 3,
flagStatsOrder: 2,
};
}
if (isMediumScreen) {
return {
gridTemplateColumns: `1fr 1fr`,
chartSpan: 2,
userTrendsOrder: 3,
flagStatsOrder: 2,
};
}
return {
gridTemplateColumns: `300px auto`,
chartSpan: 1,
userTrendsOrder: 2,
flagStatsOrder: 3,
};
};
export const ExecutiveDashboard: VFC = () => {
const { executiveDashboardData, loading, error } = useExecutiveDashboard();
const calculateFlagPerUsers = () => {
const flagPerUsers = useMemo(() => {
if (
executiveDashboardData.users.total === 0 ||
executiveDashboardData.flags.total === 0
@ -29,7 +66,10 @@ export const ExecutiveDashboard: VFC = () => {
executiveDashboardData.flags.total /
executiveDashboardData.users.total
).toFixed(1);
};
}, [executiveDashboardData]);
const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } =
useDashboardGrid();
return (
<>
@ -42,14 +82,30 @@ export const ExecutiveDashboard: VFC = () => {
}
/>
</Box>
<StyledGrid>
<StyledGrid sx={{ gridTemplateColumns }}>
<Widget title='Total users' order={1}>
<UserStats count={executiveDashboardData.users.total} />
</Widget>
<Widget title='Users' order={userTrendsOrder} span={chartSpan}>
<UsersChart
userTrends={executiveDashboardData.userTrends}
/>
</Widget>
<Widget
title='Total flags'
tooltip='Total flags represent the total ctive flags (not archived) that currently exist across all projects of your application.'
order={flagStatsOrder}
>
<FlagStats
count={executiveDashboardData.flags.total}
flagsPerUser={calculateFlagPerUsers()}
flagsPerUser={flagPerUsers}
/>
<UsersChart userTrends={executiveDashboardData.userTrends} />
<FlagsChart flagTrends={executiveDashboardData.flagTrends} />
</Widget>
<Widget title='Number of flags' order={4} span={chartSpan}>
<FlagsChart
flagTrends={executiveDashboardData.flagTrends}
/>
</Widget>
</StyledGrid>
</>
);

View File

@ -1,6 +1,5 @@
import { Settings } from '@mui/icons-material';
import { Box, Typography, styled } from '@mui/material';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
const StyledContent = styled(Box)(({ theme }) => ({
borderRadius: `${theme.shape.borderRadiusLarge}px`,
@ -88,22 +87,7 @@ export const FlagStats: React.FC<IFlagStatsProps> = ({
flagsPerUser,
}) => {
return (
<StyledContent>
<StyledHeader variant='h1'>
Total flags{' '}
<HelpIcon
htmlTooltip
tooltip={
<Box>
<Typography variant='body2'>
Total flags represent the total active flags
(not archived) that currently exist across all
projects of your application.
</Typography>
</Box>
}
/>
</StyledHeader>
<>
<StyledRingContainer>
<StyledRing>
<StyledRingContent>{count}</StyledRingContent>
@ -126,6 +110,6 @@ export const FlagStats: React.FC<IFlagStatsProps> = ({
</StyledTextContainer>
<StyledFlagCountPerUser>{flagsPerUser}</StyledFlagCountPerUser>
</StyledInsightsContainer>
</StyledContent>
</>
);
};

View File

@ -12,7 +12,7 @@ import {
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Paper, Theme, Typography, useTheme } from '@mui/material';
import { Theme, useTheme } from '@mui/material';
import {
useLocationSettings,
type ILocationSettings,
@ -116,17 +116,7 @@ const FlagsChartComponent: VFC<IFlagsChartComponentProps> = ({
);
const options = createOptions(theme, locationSettings);
return (
<Paper sx={(theme) => ({ padding: theme.spacing(4) })}>
<Typography
variant='h3'
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
>
Number of flags
</Typography>
<Line options={options} data={data} />
</Paper>
);
return <Line options={options} data={data} />;
};
ChartJS.register(

View File

@ -5,13 +5,6 @@ import { useUiFlag } from 'hooks/useUiFlag';
import React from 'react';
import { Link } from 'react-router-dom';
const StyledContent = styled(Box)(({ theme }) => ({
borderRadius: `${theme.shape.borderRadiusLarge}px`,
backgroundColor: theme.palette.background.paper,
maxWidth: 300,
padding: theme.spacing(3),
}));
const StyledUserContainer = styled(Box)(({ theme }) => ({
position: 'relative',
}));
@ -85,14 +78,9 @@ export const UserStats: React.FC<IUserStatsProps> = ({ count }) => {
return (
<>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<StyledContent>
<StyledHeader variant='h1'>Total users</StyledHeader>
<StyledUserContainer>
<StyledUserBox>
<StyledUserCount variant='h2'>
{count}
</StyledUserCount>
<StyledUserCount variant='h2'>{count}</StyledUserCount>
</StyledUserBox>
<StyledCustomShadow />
</StyledUserContainer>
@ -126,8 +114,6 @@ export const UserStats: React.FC<IUserStatsProps> = ({ count }) => {
View users <ChevronRight />
</StyledLink>
</StyledLinkContainer>
</StyledContent>
</Box>
</>
);
};

View File

@ -13,7 +13,7 @@ import {
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Paper, Theme, Typography, useTheme } from '@mui/material';
import { Theme, useTheme } from '@mui/material';
import {
useLocationSettings,
type ILocationSettings,
@ -181,23 +181,10 @@ const UsersChartComponent: VFC<IUsersChartComponentProps> = ({
);
return (
<Paper
elevation={0}
sx={(theme) => ({
padding: theme.spacing(3),
position: 'relative',
})}
>
<Typography
variant='h3'
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
>
Users
</Typography>
<>
<Line options={options} data={data} />
<ChartTooltip tooltip={tooltip} />
</Paper>
</>
);
};

View File

@ -0,0 +1,39 @@
import { FC, ReactNode } from 'react';
import { Paper, Typography } from '@mui/material';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
export const Widget: FC<{
title: ReactNode;
order?: number;
span?: number;
tooltip?: ReactNode;
}> = ({ title, order, children, span = 1, tooltip }) => (
<Paper
elevation={0}
sx={(theme) => ({
padding: 3,
borderRadius: `${theme.shape.borderRadiusLarge}px`,
order,
gridColumn: `span ${span}`,
minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
})}
>
<Typography
variant='h3'
sx={(theme) => ({
marginBottom: theme.spacing(3),
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
})}
>
{title}
<ConditionallyRender
condition={Boolean(tooltip)}
show={<HelpIcon htmlTooltip tooltip={tooltip} />}
/>
</Typography>
{children}
</Paper>
);