mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
feat: initial status box implementation (#2913)
First iteration of new status boxes
This commit is contained in:
parent
16bca1260c
commit
bf7ef62059
@ -1,13 +1,17 @@
|
|||||||
import useProject, {
|
import useProject, {
|
||||||
useProjectNameOrId,
|
useProjectNameOrId,
|
||||||
} from 'hooks/api/getters/useProject/useProject';
|
} from 'hooks/api/getters/useProject/useProject';
|
||||||
import { styled } from '@mui/material';
|
import { Box, styled } from '@mui/material';
|
||||||
import { ProjectFeatureToggles } from './ProjectFeatureToggles/ProjectFeatureToggles';
|
import { ProjectFeatureToggles } from './ProjectFeatureToggles/ProjectFeatureToggles';
|
||||||
import ProjectInfo from './ProjectInfo/ProjectInfo';
|
import ProjectInfo from './ProjectInfo/ProjectInfo';
|
||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { useLastViewedProject } from '../../../hooks/useLastViewedProject';
|
import { useLastViewedProject } from '../../../hooks/useLastViewedProject';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { StatusBox } from './ProjectStatus/StatusBox';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { ProjectStatus } from './ProjectStatus/ProjectStatus';
|
||||||
|
|
||||||
const refreshInterval = 15 * 1000;
|
const refreshInterval = 15 * 1000;
|
||||||
|
|
||||||
@ -23,6 +27,12 @@ const StyledProjectToggles = styled('div')(() => ({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledContentContainer = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
const ProjectOverview = () => {
|
const ProjectOverview = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const projectName = useProjectNameOrId(projectId);
|
const projectName = useProjectNameOrId(projectId);
|
||||||
@ -30,6 +40,7 @@ const ProjectOverview = () => {
|
|||||||
const { members, features, health, description, environments } = project;
|
const { members, features, health, description, environments } = project;
|
||||||
usePageTitle(`Project overview – ${projectName}`);
|
usePageTitle(`Project overview – ${projectName}`);
|
||||||
const { setLastViewed } = useLastViewedProject();
|
const { setLastViewed } = useLastViewedProject();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLastViewed(projectId);
|
setLastViewed(projectId);
|
||||||
@ -44,6 +55,11 @@ const ProjectOverview = () => {
|
|||||||
health={health}
|
health={health}
|
||||||
featureCount={features?.length}
|
featureCount={features?.length}
|
||||||
/>
|
/>
|
||||||
|
<StyledContentContainer>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(uiConfig?.flags.newProjectOverview)}
|
||||||
|
show={<ProjectStatus />}
|
||||||
|
/>
|
||||||
<StyledProjectToggles>
|
<StyledProjectToggles>
|
||||||
<ProjectFeatureToggles
|
<ProjectFeatureToggles
|
||||||
features={features}
|
features={features}
|
||||||
@ -51,6 +67,7 @@ const ProjectOverview = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
</StyledProjectToggles>
|
</StyledProjectToggles>
|
||||||
|
</StyledContentContainer>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Box, styled } from '@mui/material';
|
||||||
|
import { StatusBox } from './StatusBox';
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(0, 0, 2, 2),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const ProjectStatus = () => {
|
||||||
|
return (
|
||||||
|
<StyledBox>
|
||||||
|
<StatusBox title="Total changes" boxText={'86'} change={-24} />
|
||||||
|
<StatusBox
|
||||||
|
title="Total changes"
|
||||||
|
boxText={'6 days'}
|
||||||
|
change={-12}
|
||||||
|
/>{' '}
|
||||||
|
<StatusBox title="Total changes" boxText={'86'} change={-24} />
|
||||||
|
<StatusBox title="Total changes" boxText={'86'} change={-24} />
|
||||||
|
</StyledBox>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,85 @@
|
|||||||
|
import { ArrowOutward, SouthEast } from '@mui/icons-material';
|
||||||
|
import { Box, Typography, styled } from '@mui/material';
|
||||||
|
import { flexRow } from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(4, 2),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
minWidth: '240px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypographyHeader = styled(Typography)(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypographyCount = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.largeHeader,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledBoxChangeContainer = styled(Box)(({ theme }) => ({
|
||||||
|
...flexRow,
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: theme.spacing(1.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypographySubtext = styled(Typography)(({ theme }) => ({
|
||||||
|
color: theme.palette.neutral.main,
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypographyChange = styled(Typography)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IStatusBoxProps {
|
||||||
|
title: string;
|
||||||
|
boxText: string;
|
||||||
|
change: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveIcon = (change: number) => {
|
||||||
|
if (change > 0) {
|
||||||
|
return (
|
||||||
|
<ArrowOutward
|
||||||
|
sx={{ color: 'success.main', height: 18, width: 18 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <SouthEast sx={{ color: 'warning.dark', height: 18, width: 18 }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveColor = (change: number) => {
|
||||||
|
if (change > 0) {
|
||||||
|
return 'success.main';
|
||||||
|
}
|
||||||
|
return 'error.main';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StatusBox = ({ title, boxText, change }: IStatusBoxProps) => {
|
||||||
|
return (
|
||||||
|
<StyledBox>
|
||||||
|
<StyledTypographyHeader>{title}</StyledTypographyHeader>
|
||||||
|
<Box sx={{ ...flexRow }}>
|
||||||
|
<StyledTypographyCount>{boxText}</StyledTypographyCount>
|
||||||
|
<StyledBoxChangeContainer>
|
||||||
|
<Box sx={{ ...flexRow }}>
|
||||||
|
{resolveIcon(change)}
|
||||||
|
<StyledTypographyChange color={resolveColor(change)}>
|
||||||
|
{change}
|
||||||
|
</StyledTypographyChange>
|
||||||
|
</Box>
|
||||||
|
<StyledTypographySubtext>
|
||||||
|
this month
|
||||||
|
</StyledTypographySubtext>
|
||||||
|
</StyledBoxChangeContainer>
|
||||||
|
</Box>
|
||||||
|
</StyledBox>
|
||||||
|
);
|
||||||
|
};
|
@ -45,6 +45,7 @@ export interface IFlags {
|
|||||||
messageBanner?: boolean;
|
messageBanner?: boolean;
|
||||||
serviceAccounts?: boolean;
|
serviceAccounts?: boolean;
|
||||||
featuresExportImport?: boolean;
|
featuresExportImport?: boolean;
|
||||||
|
newProjectOverview?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -44,6 +44,7 @@ export default createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
|
largeHeader: '2.25rem',
|
||||||
mainHeader: '1.25rem',
|
mainHeader: '1.25rem',
|
||||||
bodySize: '1rem',
|
bodySize: '1rem',
|
||||||
smallBody: `${14 / 16}rem`,
|
smallBody: `${14 / 16}rem`,
|
||||||
|
@ -41,6 +41,7 @@ export default createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
|
largeHeader: '2rem',
|
||||||
mainHeader: '1.25rem',
|
mainHeader: '1.25rem',
|
||||||
bodySize: '1rem',
|
bodySize: '1rem',
|
||||||
smallBody: `${14 / 16}rem`,
|
smallBody: `${14 / 16}rem`,
|
||||||
|
@ -4,6 +4,7 @@ declare module '@mui/material/styles' {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
|
largeHeader: string;
|
||||||
mainHeader: string;
|
mainHeader: string;
|
||||||
bodySize: string;
|
bodySize: string;
|
||||||
smallBody: string;
|
smallBody: string;
|
||||||
|
@ -77,6 +77,7 @@ exports[`should create default config 1`] = `
|
|||||||
"maintenanceMode": false,
|
"maintenanceMode": false,
|
||||||
"messageBanner": false,
|
"messageBanner": false,
|
||||||
"networkView": false,
|
"networkView": false,
|
||||||
|
"newProjectOverview": false,
|
||||||
"proxyReturnAllToggles": false,
|
"proxyReturnAllToggles": false,
|
||||||
"responseTimeWithAppName": false,
|
"responseTimeWithAppName": false,
|
||||||
"serviceAccounts": false,
|
"serviceAccounts": false,
|
||||||
@ -95,6 +96,7 @@ exports[`should create default config 1`] = `
|
|||||||
"maintenanceMode": false,
|
"maintenanceMode": false,
|
||||||
"messageBanner": false,
|
"messageBanner": false,
|
||||||
"networkView": false,
|
"networkView": false,
|
||||||
|
"newProjectOverview": false,
|
||||||
"proxyReturnAllToggles": false,
|
"proxyReturnAllToggles": false,
|
||||||
"responseTimeWithAppName": false,
|
"responseTimeWithAppName": false,
|
||||||
"serviceAccounts": false,
|
"serviceAccounts": false,
|
||||||
|
@ -10,6 +10,10 @@ const flags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|
newProjectOverview: parseEnvVarBoolean(
|
||||||
|
process.env.NEW_PROJECT_OVERVIEW,
|
||||||
|
false,
|
||||||
|
),
|
||||||
embedProxyFrontend: parseEnvVarBoolean(
|
embedProxyFrontend: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY_FRONTEND,
|
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY_FRONTEND,
|
||||||
true,
|
true,
|
||||||
|
@ -42,6 +42,7 @@ process.nextTick(async () => {
|
|||||||
variantsPerEnvironment: true,
|
variantsPerEnvironment: true,
|
||||||
maintenance: false,
|
maintenance: false,
|
||||||
featuresExportImport: true,
|
featuresExportImport: true,
|
||||||
|
newProjectOverview: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user