diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
index 398e11b7ea..0b6a19f1d1 100644
--- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
+++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx
@@ -3,6 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { VFC } from 'react';
import { UsersChart } from './UsersChart/UsersChart';
import { FlagsChart } from './FlagsChart/FlagsChart';
+import { UserStats } from './UserStats/UserStats';
const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
@@ -26,7 +27,7 @@ export const ExecutiveDashboard: VFC = () => {
{/* Dashboard */}
- Stats
+
diff --git a/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx b/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx
new file mode 100644
index 0000000000..f959996e83
--- /dev/null
+++ b/frontend/src/component/executiveDashboard/UserStats/UserStats.tsx
@@ -0,0 +1,223 @@
+import { ChevronRight } from '@mui/icons-material';
+import { Box, Typography, styled } from '@mui/material';
+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',
+}));
+
+const StyledUserBox = styled(Box)(({ theme }) => ({
+ borderRadius: `${theme.shape.borderRadiusExtraLarge}px`,
+ backgroundColor: theme.palette.primary.main,
+ maxWidth: 300,
+ padding: theme.spacing(2),
+ marginBottom: theme.spacing(3),
+ position: 'relative',
+ zIndex: 2,
+}));
+
+const StyledCustomShadow = styled(Box)(({ theme }) => ({
+ width: '220px',
+ height: '54px',
+ backgroundColor: 'rgba(108, 101, 229, 0.30)',
+ position: 'absolute',
+ margin: '0 auto',
+ top: '45px',
+ left: '15px',
+ borderRadius: `${theme.shape.borderRadiusExtraLarge}px`,
+ zIndex: 1,
+}));
+
+const StyledUserDistributionContainer = styled(Box)(({ theme }) => ({
+ marginBottom: theme.spacing(2.5),
+}));
+
+const StyledUserCount = styled(Typography)(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ fontWeight: 'bold',
+ fontSize: theme.fontSizes.extraLargeHeader,
+ margin: 0,
+ 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',
+ gap: '12px',
+});
+
+const StyledLinkContainer = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ marginTop: theme.spacing(3),
+}));
+
+const StyledLink = styled(Link)({
+ fontWeight: 'bold',
+ textDecoration: 'none',
+ display: 'flex',
+ justifyContent: 'center',
+});
+
+export const UserStats = () => {
+ return (
+ <>
+
+
+ Total users
+
+
+ 9999
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View users
+
+
+
+
+ >
+ );
+};
+
+type UserType = 'active' | 'inactive';
+
+interface StyledLinearProgressProps {
+ type: UserType;
+}
+
+const StyledUserDistributionLine = styled(Box)(
+ ({ theme, type }) => ({
+ borderRadius: theme.shape.borderRadius,
+ height: 16,
+ backgroundColor:
+ type === 'active'
+ ? theme.palette.success.border
+ : theme.palette.warning.border,
+ }),
+);
+
+const UserDistribution = () => {
+ const getLineWidth = () => {
+ return [80, 20];
+ };
+
+ const [activeWidth, inactiveWidth] = getLineWidth();
+
+ return (
+
+
+ ({
+ width: `${inactiveWidth}%`,
+ marginLeft: theme.spacing(0.5),
+ })}
+ />
+
+ );
+};
+
+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)(
+ ({ theme, type }) => ({
+ width: 8,
+ height: 8,
+ backgroundColor:
+ type === 'active'
+ ? theme.palette.success.border
+ : theme.palette.warning.border,
+ borderRadius: `2px`,
+ marginRight: theme.spacing(1),
+ marginTop: theme.spacing(0.8),
+ }),
+);
+
+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 = ({
+ type,
+ count,
+ percentage,
+}) => {
+ return (
+
+
+
+
+
+
+ {type === 'active' ? 'Active' : 'Inactive'} users
+
+ {percentage}%
+
+
+ {count}
+
+
+
+
+ );
+};
diff --git a/frontend/src/themes/dark-theme.ts b/frontend/src/themes/dark-theme.ts
index e6535b2f3a..fd02437cee 100644
--- a/frontend/src/themes/dark-theme.ts
+++ b/frontend/src/themes/dark-theme.ts
@@ -56,6 +56,7 @@ const theme = {
},
},
fontSizes: {
+ extraLargeHeader: '2.5rem',
largeHeader: '2rem',
mainHeader: '1.25rem',
bodySize: '1rem',
diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts
index 16a8df5858..c4a35df0c2 100644
--- a/frontend/src/themes/theme.ts
+++ b/frontend/src/themes/theme.ts
@@ -48,6 +48,7 @@ export const theme = {
},
},
fontSizes: {
+ extraLargeHeader: '2.5rem',
largeHeader: '2rem',
mainHeader: '1.25rem',
bodySize: '1rem',
diff --git a/frontend/src/themes/themeTypes.ts b/frontend/src/themes/themeTypes.ts
index 4d9ffd2456..db7412ea2c 100644
--- a/frontend/src/themes/themeTypes.ts
+++ b/frontend/src/themes/themeTypes.ts
@@ -7,6 +7,7 @@ declare module '@mui/material/styles' {
* @deprecated
*/
fontSizes: {
+ extraLargeHeader: string;
largeHeader: string;
mainHeader: string;
bodySize: string;
@@ -121,13 +122,16 @@ declare module '@mui/material/styles' {
**/
variants: string[];
}
-
+ // biome-ignore lint/suspicious/noEmptyInterface: We need this to keep types from breaking
interface Theme extends CustomTheme {}
+ // biome-ignore lint/suspicious/noEmptyInterface: We need this to keep types from breaking
interface ThemeOptions extends CustomTheme {}
+ // biome-ignore lint/suspicious/noEmptyInterface: We need this to keep types from breaking
interface Palette extends CustomPalette {}
+ // biome-ignore lint/suspicious/noEmptyInterface: We need this to keep types from breaking
interface PaletteOptions extends CustomPalette {}
-
+ // biome-ignore lint/suspicious/noEmptyInterface: We need this to keep types from breaking
interface TypeBackground extends CustomTypeBackground {}
/* Extend the background object from MUI */