Skip to content

Commit

Permalink
feat: executive dashboard responsive grid (#6069)
Browse files Browse the repository at this point in the history
- unified "Widget" component
- column order dependent on screen width
  • Loading branch information
Tymek committed Jan 30, 2024
1 parent ccc41dc commit c9ac491
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 109 deletions.
82 changes: 69 additions & 13 deletions frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
@@ -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
Expand All @@ -29,7 +66,10 @@ export const ExecutiveDashboard: VFC = () => {
executiveDashboardData.flags.total /
executiveDashboardData.users.total
).toFixed(1);
};
}, [executiveDashboardData]);

const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } =
useDashboardGrid();

return (
<>
Expand All @@ -42,14 +82,30 @@ export const ExecutiveDashboard: VFC = () => {
}
/>
</Box>
<StyledGrid>
<UserStats count={executiveDashboardData.users.total} />
<FlagStats
count={executiveDashboardData.flags.total}
flagsPerUser={calculateFlagPerUsers()}
/>
<UsersChart userTrends={executiveDashboardData.userTrends} />
<FlagsChart flagTrends={executiveDashboardData.flagTrends} />
<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={flagPerUsers}
/>
</Widget>
<Widget title='Number of flags' order={4} span={chartSpan}>
<FlagsChart
flagTrends={executiveDashboardData.flagTrends}
/>
</Widget>
</StyledGrid>
</>
);
Expand Down
20 changes: 2 additions & 18 deletions frontend/src/component/executiveDashboard/FlagStats/FlagStats.tsx
@@ -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`,
Expand Down Expand Up @@ -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>
Expand All @@ -126,6 +110,6 @@ export const FlagStats: React.FC<IFlagStatsProps> = ({
</StyledTextContainer>
<StyledFlagCountPerUser>{flagsPerUser}</StyledFlagCountPerUser>
</StyledInsightsContainer>
</StyledContent>
</>
);
};
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
86 changes: 36 additions & 50 deletions frontend/src/component/executiveDashboard/UserStats/UserStats.tsx
Expand Up @@ -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',
}));
Expand Down Expand Up @@ -85,49 +78,42 @@ 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>
</StyledUserBox>
<StyledCustomShadow />
</StyledUserContainer>

<ConditionallyRender
condition={showInactiveUsers}
show={
<>
<StyledUserDistributionContainer>
<UserDistribution />
</StyledUserDistributionContainer>

<StyledDistInfoContainer>
<UserDistributionInfo
type='active'
percentage='70'
count='9999'
/>
<UserDistributionInfo
type='inactive'
percentage='30'
count='9999'
/>
</StyledDistInfoContainer>
</>
}
/>

<StyledLinkContainer>
<StyledLink to='/admin/users'>
View users <ChevronRight />
</StyledLink>
</StyledLinkContainer>
</StyledContent>
</Box>
<StyledUserContainer>
<StyledUserBox>
<StyledUserCount variant='h2'>{count}</StyledUserCount>
</StyledUserBox>
<StyledCustomShadow />
</StyledUserContainer>

<ConditionallyRender
condition={showInactiveUsers}
show={
<>
<StyledUserDistributionContainer>
<UserDistribution />
</StyledUserDistributionContainer>

<StyledDistInfoContainer>
<UserDistributionInfo
type='active'
percentage='70'
count='9999'
/>
<UserDistributionInfo
type='inactive'
percentage='30'
count='9999'
/>
</StyledDistInfoContainer>
</>
}
/>

<StyledLinkContainer>
<StyledLink to='/admin/users'>
View users <ChevronRight />
</StyledLink>
</StyledLinkContainer>
</>
);
};
Expand Down
Expand Up @@ -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,
Expand Down Expand Up @@ -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>
</>
);
};

Expand Down
39 changes: 39 additions & 0 deletions frontend/src/component/executiveDashboard/Widget/Widget.tsx
@@ -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>
);

0 comments on commit c9ac491

Please sign in to comment.