mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: project members widget (#6628)

This commit is contained in:
		
							parent
							
								
									f0e5d075a7
								
							
						
					
					
						commit
						1becfc0202
					
				@ -13,6 +13,9 @@ interface IProjectMembersWidgetProps {
 | 
				
			|||||||
    change?: number;
 | 
					    change?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @deprecated in favor of ProjectMembers.tsx
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export const ProjectMembersWidget = ({
 | 
					export const ProjectMembersWidget = ({
 | 
				
			||||||
    projectId,
 | 
					    projectId,
 | 
				
			||||||
    memberCount,
 | 
					    memberCount,
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import { ProjectInsightsStats } from './ProjectInsightsStats/ProjectInsightsStat
 | 
				
			|||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
					import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
				
			||||||
import { useProjectInsights } from 'hooks/api/getters/useProjectInsights/useProjectInsights';
 | 
					import { useProjectInsights } from 'hooks/api/getters/useProjectInsights/useProjectInsights';
 | 
				
			||||||
import useLoading from 'hooks/useLoading';
 | 
					import useLoading from 'hooks/useLoading';
 | 
				
			||||||
 | 
					import { ProjectMembers } from './ProjectMembers/ProjectMembers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Container = styled(Box)(({ theme }) => ({
 | 
					const Container = styled(Box)(({ theme }) => ({
 | 
				
			||||||
    backgroundColor: theme.palette.background.paper,
 | 
					    backgroundColor: theme.palette.background.paper,
 | 
				
			||||||
@ -56,7 +57,9 @@ export const ProjectInsights = () => {
 | 
				
			|||||||
            <NarrowContainer>
 | 
					            <NarrowContainer>
 | 
				
			||||||
                <FlagTypesUsed featureTypeCounts={data.featureTypeCounts} />
 | 
					                <FlagTypesUsed featureTypeCounts={data.featureTypeCounts} />
 | 
				
			||||||
            </NarrowContainer>
 | 
					            </NarrowContainer>
 | 
				
			||||||
            <NarrowContainer>Project members</NarrowContainer>
 | 
					            <NarrowContainer>
 | 
				
			||||||
 | 
					                <ProjectMembers projectId={projectId} members={data.members} />
 | 
				
			||||||
 | 
					            </NarrowContainer>
 | 
				
			||||||
            <WideContainer>
 | 
					            <WideContainer>
 | 
				
			||||||
                {data.changeRequests && (
 | 
					                {data.changeRequests && (
 | 
				
			||||||
                    <ChangeRequests changeRequests={data.changeRequests} />
 | 
					                    <ChangeRequests changeRequests={data.changeRequests} />
 | 
				
			||||||
 | 
				
			|||||||
@ -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(<ProjectMembers projectId={'default'} members={members} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await screen.findByText('15');
 | 
				
			||||||
 | 
					    await screen.findByText('+13');
 | 
				
			||||||
 | 
					    await screen.findByText('10');
 | 
				
			||||||
 | 
					    await screen.findByText('5');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -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 (
 | 
				
			||||||
 | 
					        <StyledProjectInfoWidgetContainer>
 | 
				
			||||||
 | 
					            <NavigationBar to={link}>
 | 
				
			||||||
 | 
					                <Typography variant='h3'>Project members</Typography>
 | 
				
			||||||
 | 
					                <KeyboardArrowRight />
 | 
				
			||||||
 | 
					            </NavigationBar>
 | 
				
			||||||
 | 
					            <Box
 | 
				
			||||||
 | 
					                sx={{
 | 
				
			||||||
 | 
					                    display: 'flex',
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <StatusBox boxText={`${currentMembers}`} change={change} />
 | 
				
			||||||
 | 
					            </Box>
 | 
				
			||||||
 | 
					            <BarContainer>
 | 
				
			||||||
 | 
					                <ActiveBar percentage={activePercentage} />
 | 
				
			||||||
 | 
					                <InactiveBar percentage={inactivePercentage} />
 | 
				
			||||||
 | 
					            </BarContainer>
 | 
				
			||||||
 | 
					            <CountContainer>
 | 
				
			||||||
 | 
					                <CountRow to={link}>
 | 
				
			||||||
 | 
					                    <StatusWithDot>
 | 
				
			||||||
 | 
					                        <Dot color={theme.palette.success.border} />
 | 
				
			||||||
 | 
					                        <Box>Active</Box>
 | 
				
			||||||
 | 
					                        <StyledCount>{active}</StyledCount>
 | 
				
			||||||
 | 
					                    </StatusWithDot>
 | 
				
			||||||
 | 
					                    <KeyboardArrowRight />
 | 
				
			||||||
 | 
					                </CountRow>
 | 
				
			||||||
 | 
					                <CountRow to={link}>
 | 
				
			||||||
 | 
					                    <StatusWithDot>
 | 
				
			||||||
 | 
					                        <Dot color={theme.palette.warning.border} />
 | 
				
			||||||
 | 
					                        <Box>Inactive</Box>
 | 
				
			||||||
 | 
					                        <StyledCount>{inactive}</StyledCount>
 | 
				
			||||||
 | 
					                    </StatusWithDot>
 | 
				
			||||||
 | 
					                    <KeyboardArrowRight />
 | 
				
			||||||
 | 
					                </CountRow>
 | 
				
			||||||
 | 
					            </CountContainer>
 | 
				
			||||||
 | 
					        </StyledProjectInfoWidgetContainer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user