diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx b/frontend/src/component/project/Project/ProjectInfo/ProjectMembersWidget.tsx
index 82fade8b04..2d9fd36e89 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 564c33eaff..e77721cb6b 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 0000000000..4540bdcecf
--- /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 0000000000..0de33b3891
--- /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}
+
+
+
+
+
+ );
+};