Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: executive dashboard responsive grid #6069

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 69 additions & 13 deletions frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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>
</>
);
};
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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>
);
Loading