mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
Project overview UI (#3034)
This commit is contained in:
parent
0a6f9f647b
commit
b98dd4d76c
@ -187,9 +187,11 @@ export const Project = () => {
|
||||
</StyledDiv>
|
||||
</StyledTopRow>
|
||||
<ConditionallyRender
|
||||
condition={!uiConfig?.flags?.newProjectOverview}
|
||||
condition={
|
||||
!Boolean(uiConfig?.flags?.newProjectOverview)
|
||||
}
|
||||
// TODO: !!! Remove entire block when removing feature flag!
|
||||
show={
|
||||
show={() => (
|
||||
<StyledColumn>
|
||||
<StyledProjectTitle>
|
||||
<div>
|
||||
@ -200,7 +202,7 @@ export const Project = () => {
|
||||
show={
|
||||
<StyledDiv>
|
||||
<StyledTitle data-loading>
|
||||
Description:{' '}
|
||||
Description:
|
||||
</StyledTitle>
|
||||
<StyledText data-loading>
|
||||
{project.description}
|
||||
@ -210,7 +212,7 @@ export const Project = () => {
|
||||
/>
|
||||
<StyledDiv>
|
||||
<StyledTitle data-loading>
|
||||
projectId:{' '}
|
||||
projectId:
|
||||
</StyledTitle>
|
||||
<StyledText data-loading>
|
||||
{projectId}
|
||||
@ -219,7 +221,7 @@ export const Project = () => {
|
||||
</div>
|
||||
</StyledProjectTitle>
|
||||
</StyledColumn>
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</StyledInnerContainer>
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Box, styled, Typography, Link } from '@mui/material';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
import {
|
||||
@ -10,17 +9,25 @@ import {
|
||||
StyledWidgetTitle,
|
||||
} from './ProjectInfo.styles';
|
||||
import { useProjectChangeRequests } from 'hooks/api/getters/useProjectChangeRequests/useProjectChangeRequests';
|
||||
import { WidgetFooterLink } from './WidgetFooterLink';
|
||||
|
||||
interface IChangeRequestsWidgetProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
const StyledContentBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
const StyledChangeBox = styled(Box)(({ theme }) => ({
|
||||
flex: 1,
|
||||
textAlign: 'left',
|
||||
padding: theme.spacing(1.5),
|
||||
marginBottom: theme.spacing(1.5),
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
alignItems: 'center',
|
||||
minWidth: 175,
|
||||
}));
|
||||
|
||||
const StyledChangeRequestStatusInfo = styled(Box)(({ theme }) => ({
|
||||
@ -42,8 +49,18 @@ const StyledInReviewCount = styled(StyledCount)(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
}));
|
||||
|
||||
const StyledSubtitle = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
marginBottom: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
const ChangeRequestsLabel = () => (
|
||||
<Typography component="span" variant="body2" color="text.secondary">
|
||||
<Typography
|
||||
component="span"
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
lineHeight={1}
|
||||
>
|
||||
change requests
|
||||
</Typography>
|
||||
);
|
||||
@ -63,33 +80,31 @@ export const ChangeRequestsWidget: FC<IChangeRequestsWidgetProps> = ({
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer ref={loadingRef}>
|
||||
<StyledWidgetTitle>Open change requests</StyledWidgetTitle>
|
||||
|
||||
<StyledChangeBox
|
||||
sx={{ background: theme => theme.palette.success.light }}
|
||||
>
|
||||
<Typography variant="body2">To be applied</Typography>
|
||||
<StyledChangeRequestStatusInfo>
|
||||
<StyledApprovedCount>{toBeApplied}</StyledApprovedCount>{' '}
|
||||
<ChangeRequestsLabel />
|
||||
</StyledChangeRequestStatusInfo>
|
||||
</StyledChangeBox>
|
||||
<StyledChangeBox
|
||||
sx={{ background: theme => theme.palette.secondary.light }}
|
||||
>
|
||||
<Typography variant="body2">To be reviewed</Typography>
|
||||
<StyledChangeRequestStatusInfo>
|
||||
<StyledInReviewCount>{toBeReviewed}</StyledInReviewCount>{' '}
|
||||
<ChangeRequestsLabel />
|
||||
</StyledChangeRequestStatusInfo>
|
||||
</StyledChangeBox>
|
||||
<Typography variant="body2" textAlign="center">
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/projects/${projectId}/change-requests`}
|
||||
<StyledContentBox>
|
||||
<StyledChangeBox
|
||||
sx={{ background: theme => theme.palette.success.light }}
|
||||
>
|
||||
View change requests
|
||||
</Link>
|
||||
</Typography>
|
||||
<StyledSubtitle>To be applied</StyledSubtitle>
|
||||
<StyledChangeRequestStatusInfo>
|
||||
<StyledApprovedCount>{toBeApplied}</StyledApprovedCount>{' '}
|
||||
<ChangeRequestsLabel />
|
||||
</StyledChangeRequestStatusInfo>
|
||||
</StyledChangeBox>
|
||||
<StyledChangeBox
|
||||
sx={{ background: theme => theme.palette.secondary.light }}
|
||||
>
|
||||
<StyledSubtitle>To be reviewed</StyledSubtitle>
|
||||
<StyledChangeRequestStatusInfo>
|
||||
<StyledInReviewCount>
|
||||
{toBeReviewed}
|
||||
</StyledInReviewCount>{' '}
|
||||
<ChangeRequestsLabel />
|
||||
</StyledChangeRequestStatusInfo>
|
||||
</StyledChangeBox>
|
||||
</StyledContentBox>
|
||||
<WidgetFooterLink to={`/projects/${projectId}/change-requests`}>
|
||||
View change requests
|
||||
</WidgetFooterLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,16 +1,10 @@
|
||||
import {
|
||||
StyledArrowIcon,
|
||||
StyledCount,
|
||||
StyledProjectInfoWidgetContainer,
|
||||
StyledDivPercentageContainer,
|
||||
StyledLink,
|
||||
StyledParagraphEmphasizedText,
|
||||
StyledWidgetTitle,
|
||||
StyledSpanLinkText,
|
||||
} from './ProjectInfo.styles';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import {
|
||||
StyledProjectInfoWidgetContainer,
|
||||
StyledWidgetTitle,
|
||||
} from './ProjectInfo.styles';
|
||||
import { WidgetFooterLink } from './WidgetFooterLink';
|
||||
|
||||
interface IHealthWidgetProps {
|
||||
projectId: string;
|
||||
@ -19,69 +13,43 @@ interface IHealthWidgetProps {
|
||||
stale?: number;
|
||||
}
|
||||
|
||||
const StyledWarning = styled('span')<{ active?: boolean }>(
|
||||
({ theme, active }) => ({
|
||||
color: active ? theme.palette.warning.dark : 'inherit',
|
||||
})
|
||||
);
|
||||
const StyledParagraphEmphasizedText = styled('p')(({ theme }) => ({
|
||||
fontSize: '1.5rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
||||
export const HealthWidget = ({
|
||||
projectId,
|
||||
health,
|
||||
total,
|
||||
stale,
|
||||
}: IHealthWidgetProps) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const StyledPercentageText = styled('p')(({ theme }) => ({
|
||||
fontSize: '1.5rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
},
|
||||
}));
|
||||
|
||||
if (uiConfig?.flags?.newProjectOverview) {
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledWidgetTitle data-loading>
|
||||
Project health
|
||||
</StyledWidgetTitle>
|
||||
<StyledDivPercentageContainer>
|
||||
export const HealthWidget = ({ projectId, health }: IHealthWidgetProps) => {
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledWidgetTitle data-loading>Project health</StyledWidgetTitle>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: theme => theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<StyledPercentageText>
|
||||
<PercentageCircle percentage={health} />
|
||||
</StyledDivPercentageContainer>
|
||||
</StyledPercentageText>
|
||||
<StyledParagraphEmphasizedText data-loading>
|
||||
{health}%
|
||||
</StyledParagraphEmphasizedText>
|
||||
<Typography data-loading>
|
||||
<StyledCount>{total}</StyledCount> toggles in total
|
||||
</Typography>
|
||||
<Typography data-loading sx={{ marginBottom: 2 }}>
|
||||
<StyledCount>
|
||||
<StyledWarning active={Boolean(stale)}>
|
||||
{stale}
|
||||
</StyledWarning>
|
||||
</StyledCount>{' '}
|
||||
<StyledWarning active={Boolean(stale)}>
|
||||
potentially stale
|
||||
</StyledWarning>
|
||||
</Typography>
|
||||
<StyledLink data-loading to={`/projects/${projectId}/health`}>
|
||||
<StyledSpanLinkText data-loading>
|
||||
View project health
|
||||
</StyledSpanLinkText>
|
||||
</StyledLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledDivPercentageContainer>
|
||||
<PercentageCircle percentage={health} />
|
||||
</StyledDivPercentageContainer>
|
||||
<StyledWidgetTitle data-loading>
|
||||
Overall health rating
|
||||
</StyledWidgetTitle>
|
||||
<StyledParagraphEmphasizedText data-loading>
|
||||
{health}%
|
||||
</StyledParagraphEmphasizedText>
|
||||
<StyledLink data-loading to={`/projects/${projectId}/health`}>
|
||||
<StyledSpanLinkText data-loading>view more </StyledSpanLinkText>
|
||||
<StyledArrowIcon data-loading />
|
||||
</StyledLink>
|
||||
</Box>
|
||||
<WidgetFooterLink to={`/projects/${projectId}/health`}>
|
||||
View project health
|
||||
</WidgetFooterLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import { StyledProjectInfoWidgetContainer } from './ProjectInfo.styles';
|
||||
|
||||
interface ILegacyHealthWidgetProps {
|
||||
projectId: string;
|
||||
health: number;
|
||||
total?: number;
|
||||
stale?: number;
|
||||
}
|
||||
|
||||
const StyledParagraphEmphasizedText = styled('p')(({ theme }) => ({
|
||||
fontSize: '1.5rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledDivPercentageContainer = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
...flexRow,
|
||||
justifyContent: 'center',
|
||||
color: theme.palette.primary.main,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
position: 'absolute',
|
||||
bottom: theme.spacing(1.5),
|
||||
right: theme.spacing(1.5),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledSpanLinkText = styled('p')(({ theme }) => ({
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledArrowIcon = styled(ArrowForwardIcon)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const LegacyHealthWidget = ({
|
||||
projectId,
|
||||
health,
|
||||
}: ILegacyHealthWidgetProps) => (
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledDivPercentageContainer>
|
||||
<PercentageCircle percentage={health} />
|
||||
</StyledDivPercentageContainer>
|
||||
<Typography data-loading sx={{ marginTop: theme => theme.spacing(2) }}>
|
||||
Overall health rating
|
||||
</Typography>
|
||||
<Box sx={{ marginBottom: theme => theme.spacing(2.5) }}>
|
||||
<StyledParagraphEmphasizedText data-loading>
|
||||
{health}%
|
||||
</StyledParagraphEmphasizedText>
|
||||
</Box>
|
||||
<StyledLink data-loading to={`/projects/${projectId}/health`}>
|
||||
<StyledSpanLinkText data-loading>view more </StyledSpanLinkText>
|
||||
<StyledArrowIcon data-loading />
|
||||
</StyledLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
@ -0,0 +1,93 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { styled } from '@mui/material';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
const StyledDivInfoContainer = styled('div')(({ theme }) => ({
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
width: '100%',
|
||||
padding: theme.spacing(3, 2, 3, 2),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
...flexRow,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
position: 'relative',
|
||||
padding: theme.spacing(1.5),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledParagraphSubtitle = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledParagraphEmphasizedText = styled('p')(({ theme }) => ({
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: theme.spacing(2),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledSpanLinkText = styled('p')(({ theme }) => ({
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
...flexRow,
|
||||
justifyContent: 'center',
|
||||
color: theme.palette.primary.main,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
position: 'absolute',
|
||||
right: theme.spacing(1.5),
|
||||
bottom: theme.spacing(1.5),
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledArrowIcon = styled(ArrowForwardIcon)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
interface ILegacyProjectMembersWidgetProps {
|
||||
projectId: string;
|
||||
memberCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const LegacyProjectMembersWidget = ({
|
||||
projectId,
|
||||
memberCount,
|
||||
}: ILegacyProjectMembersWidgetProps) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
let link = `/admin/users`;
|
||||
|
||||
if (uiConfig?.versionInfo?.current?.enterprise) {
|
||||
link = `/projects/${projectId}/settings/access`;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledDivInfoContainer>
|
||||
<StyledParagraphSubtitle data-loading>
|
||||
Project members
|
||||
</StyledParagraphSubtitle>
|
||||
<StyledParagraphEmphasizedText data-loading>
|
||||
{memberCount}
|
||||
</StyledParagraphEmphasizedText>
|
||||
<StyledLink data-loading to={link}>
|
||||
<StyledSpanLinkText data-loading>view more </StyledSpanLinkText>
|
||||
<StyledArrowIcon data-loading />
|
||||
</StyledLink>
|
||||
</StyledDivInfoContainer>
|
||||
);
|
||||
};
|
@ -1,12 +1,12 @@
|
||||
import { FC } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Box, styled, Typography, Link } from '@mui/material';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
|
||||
import {
|
||||
StyledProjectInfoWidgetContainer,
|
||||
StyledWidgetTitle,
|
||||
} from './ProjectInfo.styles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { WidgetFooterLink } from './WidgetFooterLink';
|
||||
|
||||
interface IMetaWidgetProps {
|
||||
id?: string;
|
||||
@ -35,35 +35,29 @@ export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => {
|
||||
</Typography>{' '}
|
||||
<code data-loading>{id || '__________'}</code>
|
||||
</StyledIDContainer>
|
||||
<Typography mt={1.5} textAlign="left">
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description)}
|
||||
show={
|
||||
<>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
Description:{' '}
|
||||
</Typography>
|
||||
<Typography component="span" variant="body2">
|
||||
{description}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
elseShow={
|
||||
<Typography variant="body2" textAlign="center">
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/projects/${id}/edit`}
|
||||
>
|
||||
Add description
|
||||
</Link>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Typography>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(description)}
|
||||
show={
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
marginTop: theme => theme.spacing(1.5),
|
||||
marginBottom: 0,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={!Boolean(description)}
|
||||
show={
|
||||
<WidgetFooterLink to={`/projects/${id}/edit`}>
|
||||
Add description
|
||||
</WidgetFooterLink>
|
||||
}
|
||||
/>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,74 +1,23 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
export const StyledProjectInfoSidebarContainer = styled('div')(({ theme }) => ({
|
||||
...flexRow,
|
||||
width: '225px',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
boxShadow: 'none',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'stretch',
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledDivPercentageContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: theme.spacing(2, 0),
|
||||
}));
|
||||
|
||||
export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({
|
||||
margin: '0',
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
width: '100%',
|
||||
padding: theme.spacing(3, 2, 3, 2),
|
||||
minWidth: 225,
|
||||
padding: theme.spacing(3),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
margin: theme.spacing(0, 1),
|
||||
...flexRow,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
position: 'relative',
|
||||
padding: theme.spacing(1.5),
|
||||
'&:first-of-type': {
|
||||
marginLeft: '0',
|
||||
},
|
||||
'&:last-of-type': {
|
||||
marginRight: '0',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledWidgetTitle = styled('p')(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const StyledParagraphGridRow = styled('div')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridGap: theme.spacing(1.5),
|
||||
gridTemplateColumns: `${theme.spacing(2.25)} auto auto`, //20px auto auto
|
||||
margin: theme.spacing(1, 0, 1, 0),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: 'left',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const StyledParagraphEmphasizedText = styled('p')(({ theme }) => ({
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: theme.spacing(2),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
marginBottom: theme.spacing(2.5),
|
||||
}));
|
||||
|
||||
export const StyledCount = styled('span')(({ theme }) => ({
|
||||
@ -76,25 +25,3 @@ export const StyledCount = styled('span')(({ theme }) => ({
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.text.primary,
|
||||
}));
|
||||
|
||||
export const StyledSpanLinkText = styled('p')(({ theme }) => ({
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledLink = styled(Link)(({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
...flexRow,
|
||||
justifyContent: 'center',
|
||||
color: theme.palette.primary.main,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
position: 'absolute',
|
||||
bottom: theme.spacing(1.5),
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledArrowIcon = styled(ArrowForwardIcon)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { Box, styled, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { ProjectStatsSchema } from 'openapi/models/projectStatsSchema';
|
||||
import type { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||
import { StyledProjectInfoSidebarContainer } from './ProjectInfo.styles';
|
||||
import { HealthWidget } from './HealthWidget';
|
||||
import { ToggleTypesWidget } from './ToggleTypesWidget';
|
||||
import { MetaWidget } from './MetaWidget';
|
||||
import { ProjectMembersWidget } from './ProjectMembersWidget';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ProjectStatsSchema } from 'openapi/models/projectStatsSchema';
|
||||
import { ChangeRequestsWidget } from './ChangeRequestsWidget';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { LegacyHealthWidget } from './LegacyHealthWidget';
|
||||
import { LegacyProjectMembersWidget } from './LegacyProjectMembersWidget';
|
||||
|
||||
interface IProjectInfoProps {
|
||||
id: string;
|
||||
@ -19,6 +22,23 @@ interface IProjectInfoProps {
|
||||
stats: ProjectStatsSchema;
|
||||
}
|
||||
|
||||
const StyledProjectInfoSidebarContainer = styled(Box)(({ theme }) => ({
|
||||
...flexRow,
|
||||
width: '225px',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
boxShadow: 'none',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
alignItems: 'stretch',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'flex',
|
||||
},
|
||||
}));
|
||||
|
||||
const ProjectInfo = ({
|
||||
id,
|
||||
description,
|
||||
@ -28,29 +48,74 @@ const ProjectInfo = ({
|
||||
stats,
|
||||
}: IProjectInfoProps) => {
|
||||
const { uiConfig, isEnterprise } = useUiConfig();
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const showChangeRequestsWidget = isEnterprise();
|
||||
const showProjectMembersWidget = id !== DEFAULT_PROJECT_ID;
|
||||
const fitMoreColumns =
|
||||
(!showChangeRequestsWidget && !showProjectMembersWidget) ||
|
||||
(isSmallScreen && showChangeRequestsWidget && showProjectMembersWidget);
|
||||
|
||||
if (!Boolean(uiConfig?.flags.newProjectOverview)) {
|
||||
return (
|
||||
<aside>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns:
|
||||
'repeat(auto-fit, minmax(225px, 1fr))',
|
||||
gap: theme => theme.spacing(2),
|
||||
paddingBottom: theme => theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<LegacyHealthWidget projectId={id} health={health} />
|
||||
<ConditionallyRender
|
||||
condition={showProjectMembersWidget}
|
||||
show={
|
||||
<LegacyProjectMembersWidget
|
||||
projectId={id}
|
||||
memberCount={memberCount}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<StyledProjectInfoSidebarContainer>
|
||||
<StyledProjectInfoSidebarContainer
|
||||
sx={
|
||||
fitMoreColumns
|
||||
? {
|
||||
gridTemplateColumns:
|
||||
'repeat(auto-fill, minmax(225px, 1fr))',
|
||||
}
|
||||
: { gridTemplateColumns: 'repeat(2, 1fr)' }
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
isEnterprise() &&
|
||||
Boolean(uiConfig?.flags.newProjectOverview)
|
||||
condition={showChangeRequestsWidget}
|
||||
show={
|
||||
<Box
|
||||
sx={{
|
||||
gridColumnStart: showProjectMembersWidget
|
||||
? 'span 2'
|
||||
: 'span 1',
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<ChangeRequestsWidget projectId={id} />
|
||||
</Box>
|
||||
}
|
||||
show={<ChangeRequestsWidget projectId={id} />}
|
||||
/>
|
||||
<MetaWidget id={id} description={description} />
|
||||
<HealthWidget projectId={id} health={health} />
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig?.flags.newProjectOverview)}
|
||||
show={<MetaWidget id={id} description={description} />}
|
||||
/>
|
||||
<HealthWidget
|
||||
projectId={id}
|
||||
health={health}
|
||||
total={features.length}
|
||||
stale={features.filter(feature => feature.stale).length}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={id !== DEFAULT_PROJECT_ID}
|
||||
condition={showProjectMembersWidget}
|
||||
show={
|
||||
<ProjectMembersWidget
|
||||
projectId={id}
|
||||
@ -59,10 +124,7 @@ const ProjectInfo = ({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig?.flags.newProjectOverview)}
|
||||
show={<ToggleTypesWidget features={features} />}
|
||||
/>
|
||||
<ToggleTypesWidget features={features} />
|
||||
</StyledProjectInfoSidebarContainer>
|
||||
</aside>
|
||||
);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {
|
||||
StyledLink,
|
||||
StyledProjectInfoWidgetContainer,
|
||||
StyledSpanLinkText,
|
||||
StyledWidgetTitle,
|
||||
} from './ProjectInfo.styles';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { StatusBox } from '../ProjectStats/StatusBox';
|
||||
import { WidgetFooterLink } from './WidgetFooterLink';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
interface IProjectMembersWidgetProps {
|
||||
projectId: string;
|
||||
@ -26,20 +27,17 @@ export const ProjectMembersWidget = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledProjectInfoWidgetContainer
|
||||
sx={{ padding: theme => theme.spacing(0, 0, 3, 0) }}
|
||||
>
|
||||
<StatusBox
|
||||
title={'Project members'}
|
||||
boxText={`${memberCount}`}
|
||||
change={change}
|
||||
fullWidthBodyText
|
||||
/>
|
||||
<StyledLink data-loading to={link}>
|
||||
<StyledSpanLinkText data-loading>
|
||||
View all members
|
||||
</StyledSpanLinkText>
|
||||
</StyledLink>
|
||||
<StyledProjectInfoWidgetContainer>
|
||||
<StyledWidgetTitle data-loading>Project members</StyledWidgetTitle>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<StatusBox boxText={`${memberCount}`} change={change} />
|
||||
</Box>
|
||||
<WidgetFooterLink to={link}>View all members</WidgetFooterLink>
|
||||
</StyledProjectInfoWidgetContainer>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,6 @@ import type { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||
import {
|
||||
StyledCount,
|
||||
StyledParagraphGridRow,
|
||||
StyledProjectInfoWidgetContainer,
|
||||
StyledWidgetTitle,
|
||||
} from './ProjectInfo.styles';
|
||||
@ -14,8 +13,10 @@ export interface IToggleTypesWidgetProps {
|
||||
features: IFeatureToggleListItem[];
|
||||
}
|
||||
|
||||
const StyledTypeCount = styled(StyledCount)(() => ({
|
||||
const StyledTypeCount = styled(StyledCount)(({ theme }) => ({
|
||||
marginLeft: 'auto',
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
interface IToggleTypeRowProps {
|
||||
@ -23,10 +24,26 @@ interface IToggleTypeRowProps {
|
||||
Icon: OverridableComponent<SvgIconTypeMap>;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const StyledParagraphGridRow = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1.5),
|
||||
width: '100%',
|
||||
gridTemplateColumns: `${theme.spacing(2.5)} auto auto`, //20px auto auto
|
||||
margin: theme.spacing(1, 0),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.text.secondary,
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
margin: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const ToggleTypesRow = ({ type, Icon, count }: IToggleTypeRowProps) => {
|
||||
const getTitleText = (str: string) => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).replace('-', ' ');
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledParagraphGridRow data-loading>
|
||||
<Icon fontSize="small" data-loading />
|
||||
|
@ -0,0 +1,28 @@
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link, Typography } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
|
||||
interface IWidgetFooterLinkProps {
|
||||
to: string;
|
||||
}
|
||||
|
||||
export const WidgetFooterLink: FC<IWidgetFooterLinkProps> = ({
|
||||
children,
|
||||
to,
|
||||
}) => {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
textAlign="center"
|
||||
sx={{
|
||||
paddingTop: theme => theme.spacing(2.5),
|
||||
marginTop: 'auto',
|
||||
justifySelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<Link component={RouterLink} to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
</Typography>
|
||||
);
|
||||
};
|
@ -1,21 +1,39 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { ProjectStatsSchema } from 'openapi/models';
|
||||
import { object } from 'prop-types';
|
||||
import { StatusBox } from './StatusBox';
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(0, 0, 2, 2),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
display: 'grid',
|
||||
gap: theme.spacing(2),
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
flexWrap: 'wrap',
|
||||
[theme.breakpoints.down('lg')]: {
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
paddingLeft: 0,
|
||||
padding: theme.spacing(0, 0, 2),
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledWidget = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||
[theme.breakpoints.down('lg')]: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
interface IProjectStatsProps {
|
||||
stats: ProjectStatsSchema;
|
||||
}
|
||||
@ -47,32 +65,53 @@ export const ProjectStats = ({ stats }: IProjectStatsProps) => {
|
||||
|
||||
return (
|
||||
<StyledBox>
|
||||
<StatusBox
|
||||
title="Total changes"
|
||||
boxText={String(projectActivityCurrentWindow)}
|
||||
change={
|
||||
projectActivityCurrentWindow - projectActivityPastWindow
|
||||
}
|
||||
/>
|
||||
<StatusBox
|
||||
title="Avg. time to production"
|
||||
boxText={`${avgTimeToProdCurrentWindow} days`}
|
||||
change={calculatePercentage(
|
||||
avgTimeToProdCurrentWindow,
|
||||
avgTimeToProdPastWindow
|
||||
)}
|
||||
percentage
|
||||
/>{' '}
|
||||
<StatusBox
|
||||
title="Features created"
|
||||
boxText={String(createdCurrentWindow)}
|
||||
change={createdCurrentWindow - createdPastWindow}
|
||||
/>
|
||||
<StatusBox
|
||||
title="Features archived"
|
||||
boxText={String(archivedCurrentWindow)}
|
||||
change={archivedCurrentWindow - archivedPastWindow}
|
||||
/>
|
||||
<StyledWidget>
|
||||
<StatusBox
|
||||
title="Total changes"
|
||||
boxText={String(projectActivityCurrentWindow)}
|
||||
change={
|
||||
projectActivityCurrentWindow -
|
||||
projectActivityPastWindow -
|
||||
20
|
||||
}
|
||||
/>
|
||||
</StyledWidget>
|
||||
<StyledWidget>
|
||||
<StatusBox
|
||||
title="Avg. time to production"
|
||||
boxText={
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme => theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
{avgTimeToProdCurrentWindow}{' '}
|
||||
<Typography component="span">days</Typography>
|
||||
</Box>
|
||||
}
|
||||
change={calculatePercentage(
|
||||
avgTimeToProdCurrentWindow,
|
||||
avgTimeToProdPastWindow
|
||||
)}
|
||||
percentage
|
||||
/>
|
||||
</StyledWidget>
|
||||
<StyledWidget>
|
||||
<StatusBox
|
||||
title="Features created"
|
||||
boxText={String(createdCurrentWindow)}
|
||||
change={createdCurrentWindow - createdPastWindow}
|
||||
/>
|
||||
</StyledWidget>
|
||||
<StyledWidget>
|
||||
<StatusBox
|
||||
title="Features archived"
|
||||
boxText={String(archivedCurrentWindow)}
|
||||
change={archivedCurrentWindow - archivedPastWindow}
|
||||
/>
|
||||
</StyledWidget>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
@ -1,80 +1,56 @@
|
||||
import { ArrowOutward, SouthEast } from '@mui/icons-material';
|
||||
import type { ReactNode } from 'react';
|
||||
import { CallMade, SouthEast } from '@mui/icons-material';
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(4, 2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
minWidth: '24%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||
[theme.breakpoints.down('lg')]: {
|
||||
minWidth: '49%',
|
||||
padding: theme.spacing(2),
|
||||
':nth-child(n+3)': {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
':nth-child(n+2)': {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTypographyHeader = styled(Typography)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2.5),
|
||||
}));
|
||||
|
||||
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),
|
||||
marginLeft: theme.spacing(2.5),
|
||||
}));
|
||||
|
||||
const StyledTypographySubtext = styled(Typography)(({ theme }) => ({
|
||||
color: theme.palette.neutral.main,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
}));
|
||||
|
||||
const StyledTypographyChange = styled(Typography)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}));
|
||||
|
||||
interface IStatusBoxProps {
|
||||
title: string;
|
||||
boxText: string;
|
||||
title?: string;
|
||||
boxText: ReactNode;
|
||||
change: number;
|
||||
percentage?: boolean;
|
||||
fullWidthBodyText?: boolean;
|
||||
}
|
||||
|
||||
const resolveIcon = (change: number) => {
|
||||
if (change > 0) {
|
||||
return (
|
||||
<ArrowOutward
|
||||
sx={{ color: 'success.main', height: 18, width: 18 }}
|
||||
/>
|
||||
<CallMade sx={{ color: 'success.dark', height: 20, width: 20 }} />
|
||||
);
|
||||
}
|
||||
return <SouthEast sx={{ color: 'warning.dark', height: 18, width: 18 }} />;
|
||||
return <SouthEast sx={{ color: 'warning.dark', height: 20, width: 20 }} />;
|
||||
};
|
||||
|
||||
const resolveColor = (change: number) => {
|
||||
if (change > 0) {
|
||||
return 'success.main';
|
||||
return 'success.dark';
|
||||
}
|
||||
return 'error.main';
|
||||
return 'warning.dark';
|
||||
};
|
||||
|
||||
export const StatusBox = ({
|
||||
@ -82,52 +58,51 @@ export const StatusBox = ({
|
||||
boxText,
|
||||
change,
|
||||
percentage,
|
||||
fullWidthBodyText,
|
||||
}: IStatusBoxProps) => {
|
||||
return (
|
||||
<StyledBox>
|
||||
<StyledTypographyHeader>{title}</StyledTypographyHeader>
|
||||
<Box
|
||||
sx={{
|
||||
...flexRow,
|
||||
justifyContent: fullWidthBodyText
|
||||
? 'space-between'
|
||||
: 'center',
|
||||
width: fullWidthBodyText ? '65%' : 'auto',
|
||||
}}
|
||||
>
|
||||
<StyledTypographyCount>{boxText}</StyledTypographyCount>
|
||||
<ConditionallyRender
|
||||
condition={change !== 0}
|
||||
show={
|
||||
<StyledBoxChangeContainer>
|
||||
<Box
|
||||
sx={{
|
||||
...flexRow,
|
||||
}}
|
||||
}: IStatusBoxProps) => (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(title)}
|
||||
show={<StyledTypographyHeader>{title}</StyledTypographyHeader>}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
...flexRow,
|
||||
justifyContent: 'center',
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<StyledTypographyCount>{boxText}</StyledTypographyCount>
|
||||
<ConditionallyRender
|
||||
condition={change !== 0}
|
||||
show={
|
||||
<StyledBoxChangeContainer>
|
||||
<Box
|
||||
sx={{
|
||||
...flexRow,
|
||||
}}
|
||||
>
|
||||
{resolveIcon(change)}
|
||||
<StyledTypographyChange
|
||||
color={resolveColor(change)}
|
||||
>
|
||||
{resolveIcon(change)}
|
||||
<StyledTypographyChange
|
||||
color={resolveColor(change)}
|
||||
>
|
||||
{change}
|
||||
{percentage ? '%' : ''}
|
||||
</StyledTypographyChange>
|
||||
</Box>
|
||||
<StyledTypographySubtext>
|
||||
this month
|
||||
</StyledTypographySubtext>
|
||||
</StyledBoxChangeContainer>
|
||||
}
|
||||
elseShow={
|
||||
<StyledBoxChangeContainer>
|
||||
<StyledTypographySubtext>
|
||||
No change
|
||||
</StyledTypographySubtext>
|
||||
</StyledBoxChangeContainer>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
{change > 0 ? '+' : ''}
|
||||
{change}
|
||||
{percentage ? '%' : ''}
|
||||
</StyledTypographyChange>
|
||||
</Box>
|
||||
<StyledTypographySubtext>
|
||||
this month
|
||||
</StyledTypographySubtext>
|
||||
</StyledBoxChangeContainer>
|
||||
}
|
||||
elseShow={
|
||||
<StyledBoxChangeContainer>
|
||||
<StyledTypographySubtext>
|
||||
No change
|
||||
</StyledTypographySubtext>
|
||||
</StyledBoxChangeContainer>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user