1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/frontend/src/component/personalDashboard/MyProjects.tsx
Thomas Heartman 8a7bf865d3
fix: handle project fetching error (#8375)
Work in progress
2024-10-08 08:46:14 +02:00

251 lines
8.6 KiB
TypeScript

import {
Box,
IconButton,
Link,
List,
ListItem,
ListItemButton,
Typography,
} from '@mui/material';
import { Badge } from '../common/Badge/Badge';
import { ProjectIcon } from '../common/ProjectIcon/ProjectIcon';
import LinkIcon from '@mui/icons-material/ArrowForward';
import { ProjectSetupComplete } from './ProjectSetupComplete';
import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
import { LatestProjectEvents } from './LatestProjectEvents';
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
import { forwardRef, useEffect, useRef, type FC } from 'react';
import { StyledCardTitle } from './PersonalDashboard';
import type {
PersonalDashboardProjectDetailsSchema,
PersonalDashboardSchemaAdminsItem,
PersonalDashboardSchemaProjectsItem,
} from '../../openapi';
import {
ContentGridContainer,
EmptyGridItem,
ListItemBox,
listItemStyle,
ProjectGrid,
GridItem,
SpacedGridItem,
} from './Grid';
import { ContactAdmins, DataError } from './ProjectDetailsError';
const ActiveProjectDetails: FC<{
project: PersonalDashboardSchemaProjectsItem;
}> = ({ project }) => {
return (
<Box sx={{ display: 'flex', gap: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant='subtitle2' color='primary'>
{project.featureCount}
</Typography>
<Typography variant='caption' color='text.secondary'>
flags
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant='subtitle2' color='primary'>
{project.memberCount}
</Typography>
<Typography variant='caption' color='text.secondary'>
members
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant='subtitle2' color='primary'>
{project.health}%
</Typography>
<Typography variant='caption' color='text.secondary'>
health
</Typography>
</Box>
</Box>
);
};
const ProjectListItem: FC<{
project: PersonalDashboardSchemaProjectsItem;
selected: boolean;
onClick: () => void;
}> = ({ project, selected, onClick }) => {
const activeProjectRef = useRef<HTMLLIElement>(null);
useEffect(() => {
if (activeProjectRef.current) {
activeProjectRef.current.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
}, []);
return (
<ListItem
disablePadding={true}
sx={{ mb: 1 }}
ref={selected ? activeProjectRef : null}
>
<ListItemButton
sx={listItemStyle}
selected={selected}
onClick={onClick}
>
<ListItemBox>
<ProjectIcon color='primary' />
<StyledCardTitle>{project.name}</StyledCardTitle>
<IconButton
component={Link}
href={`projects/${project.id}`}
size='small'
sx={{ ml: 'auto' }}
>
<LinkIcon titleAccess={`projects/${project.id}`} />
</IconButton>
</ListItemBox>
{selected ? <ActiveProjectDetails project={project} /> : null}
</ListItemButton>
</ListItem>
);
};
export const MyProjects = forwardRef<
HTMLDivElement,
{
projects: PersonalDashboardSchemaProjectsItem[];
personalDashboardProjectDetails?: PersonalDashboardProjectDetailsSchema;
activeProject: string;
setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[];
}
>(
(
{
projects,
personalDashboardProjectDetails,
setActiveProject,
activeProject,
admins,
},
ref,
) => {
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ??
'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';
const error = personalDashboardProjectDetails === undefined;
const box1Content = () => {
if (error) {
return <DataError project={activeProject} />;
}
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<ProjectSetupComplete
project={activeProject}
insights={personalDashboardProjectDetails.insights}
/>
);
} else if (
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'loading'
) {
return <CreateFlag project={activeProject} />;
} else if (activeProjectStage === 'first-flag-created') {
return <ExistingFlag project={activeProject} />;
}
};
const box2Content = () => {
if (error) {
return <ContactAdmins admins={admins} />;
}
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
}
/>
);
}
if (setupIncomplete || activeProjectStage === 'loading') {
return <ConnectSDK project={activeProject} />;
}
};
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<GridItem gridArea='title'>
<Typography variant='h3'>My projects</Typography>
</GridItem>
<GridItem
gridArea='onboarding'
sx={{
display: 'flex',
justifyContent: 'flex-end',
}}
>
{setupIncomplete ? (
<Badge color='warning'>Setup incomplete</Badge>
) : null}
{error ? (
<Badge color='error'>Setup state unknown</Badge>
) : null}
</GridItem>
<SpacedGridItem gridArea='projects'>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() => setActiveProject(project.id)}
/>
))}
</List>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
{box1Content()}
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
{box2Content()}
</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
<RoleAndOwnerInfo
roles={
personalDashboardProjectDetails?.roles.map(
(role) => role.name,
) ?? []
}
owners={
personalDashboardProjectDetails?.owners ?? [
{ ownerType: 'user', name: '?' },
]
}
/>
</GridItem>
</ProjectGrid>
</ContentGridContainer>
);
},
);