diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx b/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx index 82fade8b048..2d9fd36e898 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx @@ -13,6 +13,9 @@ interface IProjectMembersWidgetProps { change?: number; } +/** + * @deprecated in favor of ProjectMembers.tsx + */ export const ProjectMembersWidget = ({ projectId, memberCount, diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx index 564c33eaff3..e77721cb6b8 100644 --- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx @@ -7,6 +7,7 @@ import { ProjectInsightsStats } from './ProjectInsightsStats/ProjectInsightsStat import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useProjectInsights } from 'hooks/api/getters/useProjectInsights/useProjectInsights'; import useLoading from 'hooks/useLoading'; +import { ProjectMembers } from './ProjectMembers/ProjectMembers'; const Container = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -56,7 +57,9 @@ export const ProjectInsights = () => { - Project members + + + {data.changeRequests && ( diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx new file mode 100644 index 00000000000..4540bdcecf0 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx @@ -0,0 +1,18 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { ProjectMembers } from './ProjectMembers'; + +test('Show outdated project members', async () => { + const members = { + active: 10, + totalPreviousMonth: 2, + inactive: 5, + }; + + render(); + + await screen.findByText('15'); + await screen.findByText('+13'); + await screen.findByText('10'); + await screen.findByText('5'); +}); diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx new file mode 100644 index 00000000000..0de33b38914 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx @@ -0,0 +1,149 @@ +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { Box, styled, Typography, useTheme } from '@mui/material'; +import { StatusBox } from '../ProjectInsightsStats/StatusBox'; +import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; +import { Link } from 'react-router-dom'; +import type { ProjectInsightsSchemaMembers } from '../../../../../openapi'; + +interface IProjectMembersProps { + members: ProjectInsightsSchemaMembers; + projectId: string; +} + +const NavigationBar = styled(Link)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + textDecoration: 'none', + color: theme.palette.text.primary, +})); + +export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2.5), +})); + +export const BarContainer = styled('div')(({ theme }) => ({ + width: '100%', + height: '20px', + display: 'flex', +})); + +const ActiveBar = styled('span', { + shouldForwardProp: (prop) => prop !== 'percentage', +})<{ + percentage: number; +}>(({ theme, percentage }) => ({ + width: `${percentage - 1}%`, + backgroundColor: theme.palette.success.border, + borderRadius: theme.shape.borderRadius, +})); + +const InactiveBar = styled('span', { + shouldForwardProp: (prop) => prop !== 'percentage', +})<{ + percentage: number; +}>(({ theme, percentage }) => ({ + width: `${percentage - 1}%`, + backgroundColor: theme.palette.warning.border, + marginLeft: 'auto', + borderRadius: theme.shape.borderRadius, +})); + +export const CountContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1.5), +})); + +export const CountRow = styled(NavigationBar)(({ theme }) => ({ + display: 'flex', + padding: theme.spacing(0.5, 1, 0, 2), + alignItems: 'center', + gap: theme.spacing(3), + alignSelf: 'stretch', + borderRadius: theme.shape.borderRadiusMedium, + background: '#F7F7FA', +})); + +const StatusWithDot = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +})); + +const Dot = styled('span', { + shouldForwardProp: (prop) => prop !== 'color', +})<{ + color?: string; +}>(({ theme, color }) => ({ + height: '15px', + width: '15px', + borderRadius: '50%', + display: 'inline-block', + backgroundColor: color, +})); + +export const StyledCount = styled('span')(({ theme }) => ({ + fontSize: theme.typography.h1.fontSize, + fontWeight: theme.typography.fontWeightRegular, + color: theme.palette.text.primary, +})); + +export const ProjectMembers = ({ + members, + projectId, +}: IProjectMembersProps) => { + const { uiConfig } = useUiConfig(); + const theme = useTheme(); + + const link = uiConfig?.versionInfo?.current?.enterprise + ? `/projects/${projectId}/settings/access` + : `/admin/users`; + + const { active, totalPreviousMonth, inactive } = members; + + const currentMembers = active + inactive; + const change = currentMembers - (totalPreviousMonth || 0); + + const activePercentage = (active / currentMembers) * 100; + const inactivePercentage = (inactive / currentMembers) * 100; + + return ( + + + Project members + + + + + + + + + + + + + + Active + {active} + + + + + + + Inactive + {inactive} + + + + + + ); +};