mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
Projects archive UI (#7842)
Closes [issue/1-2666](https://linear.app/unleash/issue/1-2666/archived-projects-view)
This commit is contained in:
parent
3c45a4b2a9
commit
f2b7e0278d
@ -32,7 +32,7 @@ const BreadcrumbNav = () => {
|
||||
const { isAdmin } = useContext(AccessContext);
|
||||
const location = useLocation();
|
||||
|
||||
const paths = location.pathname
|
||||
let paths = location.pathname
|
||||
.split('/')
|
||||
.filter((item) => item)
|
||||
.filter(
|
||||
@ -55,9 +55,15 @@ const BreadcrumbNav = () => {
|
||||
.map(decodeURI);
|
||||
|
||||
if (location.pathname === '/insights') {
|
||||
// Because of sticky header in Insights
|
||||
return null;
|
||||
}
|
||||
|
||||
if (paths.length === 1 && paths[0] === 'projects-archive') {
|
||||
// It's not possible to use `projects/archive`, because it's :projectId path
|
||||
paths = ['projects', 'archive'];
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledBreadcrumbContainer>
|
||||
<ConditionallyRender
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { ComponentProps, FC } from 'react';
|
||||
import { SvgIcon } from '@mui/material';
|
||||
import { ReactComponent as Svg } from 'assets/icons/projectIconSmall.svg';
|
||||
|
||||
export const ProjectIcon = () => (
|
||||
<SvgIcon component={Svg} viewBox={'0 0 14 10'} />
|
||||
);
|
||||
export const ProjectIcon: FC<ComponentProps<typeof SvgIcon>> = ({
|
||||
...props
|
||||
}) => <SvgIcon component={Svg} viewBox={'0 0 14 10'} {...props} />;
|
||||
|
@ -98,6 +98,13 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"title": "Projects",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
"path": "/projects-archive",
|
||||
"title": "Projects archive",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {
|
||||
|
@ -9,6 +9,7 @@ import { NewUser } from 'component/user/NewUser/NewUser';
|
||||
import ResetPassword from 'component/user/ResetPassword/ResetPassword';
|
||||
import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword';
|
||||
import { ProjectListNew } from 'component/project/ProjectList/ProjectList';
|
||||
import { ArchiveProjectList } from 'component/project/ProjectList/ArchiveProjectList';
|
||||
import RedirectArchive from 'component/archive/RedirectArchive';
|
||||
import CreateEnvironment from 'component/environments/CreateEnvironment/CreateEnvironment';
|
||||
import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironment';
|
||||
@ -125,6 +126,13 @@ export const routes: IRoute[] = [
|
||||
type: 'protected',
|
||||
menu: { mobile: true },
|
||||
},
|
||||
{
|
||||
path: '/projects-archive',
|
||||
title: 'Projects archive',
|
||||
component: ArchiveProjectList,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
|
||||
// Features
|
||||
{
|
||||
|
@ -4,7 +4,8 @@ import Delete from '@mui/icons-material/Delete';
|
||||
import Edit from '@mui/icons-material/Edit';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
|
||||
export const StyledProjectCard = styled(Card)(({ theme }) => ({
|
||||
export const StyledProjectCard = styled(Card)<{ disabled?: boolean }>(
|
||||
({ theme, disabled = false }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
@ -14,12 +15,16 @@ export const StyledProjectCard = styled(Card)(({ theme }) => ({
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
'&:hover': {
|
||||
transition: 'background-color 0.2s ease-in-out',
|
||||
backgroundColor: disabled
|
||||
? theme.palette.neutral.light
|
||||
: theme.palette.background.default,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
},
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
export const StyledProjectCardBody = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 2, 2, 2),
|
||||
@ -72,11 +77,13 @@ export const StyledDivInfo = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0, 1),
|
||||
}));
|
||||
|
||||
export const StyledParagraphInfo = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.primary.dark,
|
||||
fontWeight: 'bold',
|
||||
export const StyledParagraphInfo = styled('p')<{ disabled?: boolean }>(
|
||||
({ theme, disabled = false }) => ({
|
||||
color: disabled ? 'inherit' : theme.palette.primary.dark,
|
||||
fontWeight: disabled ? 'normal' : 'bold',
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
export const StyledIconBox = styled(Box)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
@ -87,3 +94,8 @@ export const StyledIconBox = styled(Box)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
height: '100%',
|
||||
}));
|
||||
|
||||
export const StyledActions = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
marginRight: theme.spacing(2),
|
||||
}));
|
||||
|
@ -20,7 +20,7 @@ interface IProjectCardProps {
|
||||
name: string;
|
||||
featureCount: number;
|
||||
health: number;
|
||||
memberCount: number;
|
||||
memberCount?: number;
|
||||
id: string;
|
||||
onHover: () => void;
|
||||
isFavorite?: boolean;
|
||||
@ -32,7 +32,7 @@ export const ProjectCard = ({
|
||||
name,
|
||||
featureCount,
|
||||
health,
|
||||
memberCount,
|
||||
memberCount = 0,
|
||||
onHover,
|
||||
id,
|
||||
mode,
|
||||
|
@ -0,0 +1,140 @@
|
||||
import type { FC } from 'react';
|
||||
import {
|
||||
StyledProjectCard,
|
||||
StyledDivHeader,
|
||||
StyledBox,
|
||||
StyledCardTitle,
|
||||
StyledDivInfo,
|
||||
StyledParagraphInfo,
|
||||
StyledProjectCardBody,
|
||||
StyledIconBox,
|
||||
StyledActions,
|
||||
} from './NewProjectCard.styles';
|
||||
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
|
||||
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
|
||||
import { ProjectOwners } from './ProjectOwners/ProjectOwners';
|
||||
import type { ProjectSchemaOwners } from 'openapi';
|
||||
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
|
||||
import { formatDateYMDHM } from 'utils/formatDate';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { Box, Link, Tooltip } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import {
|
||||
CREATE_PROJECT,
|
||||
DELETE_PROJECT,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import Undo from '@mui/icons-material/Undo';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import Delete from '@mui/icons-material/Delete';
|
||||
|
||||
interface IProjectArchiveCardProps {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt?: string;
|
||||
archivedAt?: string;
|
||||
featureCount: number;
|
||||
onRevive: () => void;
|
||||
onDelete: () => void;
|
||||
mode: string;
|
||||
owners?: ProjectSchemaOwners;
|
||||
}
|
||||
|
||||
export const ProjectArchiveCard: FC<IProjectArchiveCardProps> = ({
|
||||
id,
|
||||
name,
|
||||
archivedAt,
|
||||
featureCount = 0,
|
||||
onRevive,
|
||||
onDelete,
|
||||
mode,
|
||||
owners,
|
||||
}) => {
|
||||
const { locationSettings } = useLocationSettings();
|
||||
const Actions: FC<{
|
||||
id: string;
|
||||
}> = ({ id }) => (
|
||||
<StyledActions>
|
||||
<PermissionIconButton
|
||||
onClick={onRevive}
|
||||
projectId={id}
|
||||
permission={CREATE_PROJECT}
|
||||
tooltipProps={{ title: 'Restore project' }}
|
||||
data-testid={`revive-feature-flag-button`}
|
||||
>
|
||||
<Undo />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_PROJECT}
|
||||
projectId={id}
|
||||
tooltipProps={{ title: 'Permanently delete project' }}
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
</StyledActions>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledProjectCard disabled>
|
||||
<StyledProjectCardBody>
|
||||
<StyledDivHeader>
|
||||
<StyledIconBox>
|
||||
<ProjectIcon color='action' />
|
||||
</StyledIconBox>
|
||||
<StyledBox data-loading>
|
||||
<StyledCardTitle>{name}</StyledCardTitle>
|
||||
</StyledBox>
|
||||
<ProjectModeBadge mode={mode} />
|
||||
</StyledDivHeader>
|
||||
<StyledDivInfo>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/archive?search=project%3A${encodeURI(id)}`}
|
||||
>
|
||||
<StyledParagraphInfo disabled data-loading>
|
||||
{featureCount}
|
||||
</StyledParagraphInfo>
|
||||
<p data-loading>
|
||||
archived {featureCount === 1 ? 'flag' : 'flags'}
|
||||
</p>
|
||||
</Link>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(archivedAt)}
|
||||
show={
|
||||
<Tooltip
|
||||
title={formatDateYMDHM(
|
||||
parseISO(archivedAt as string),
|
||||
locationSettings.locale,
|
||||
)}
|
||||
arrow
|
||||
>
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
})}
|
||||
>
|
||||
<StyledParagraphInfo disabled data-loading>
|
||||
Archived
|
||||
</StyledParagraphInfo>
|
||||
<p data-loading>
|
||||
<TimeAgo
|
||||
date={
|
||||
new Date(archivedAt as string)
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</StyledDivInfo>
|
||||
</StyledProjectCardBody>
|
||||
<ProjectCardFooter id={id} Actions={Actions} disabled>
|
||||
<ProjectOwners owners={owners} />
|
||||
</ProjectCardFooter>
|
||||
</StyledProjectCard>
|
||||
);
|
||||
};
|
@ -10,26 +10,36 @@ interface IProjectCardFooterProps {
|
||||
id: string;
|
||||
isFavorite?: boolean;
|
||||
children?: React.ReactNode;
|
||||
Actions?: FC<{ id: string; isFavorite?: boolean }>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const StyledFooter = styled(Box)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto 1fr auto',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(1.5, 3, 2.5, 3),
|
||||
background: theme.palette.envAccordion.expanded,
|
||||
const StyledFooter = styled(Box)<{ disabled: boolean }>(
|
||||
({ theme, disabled }) => ({
|
||||
display: 'flex',
|
||||
background: disabled
|
||||
? theme.palette.background.paper
|
||||
: theme.palette.envAccordion.expanded,
|
||||
boxShadow: theme.boxShadows.accordionFooter,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}),
|
||||
);
|
||||
|
||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1.5, 0, 2.5, 3),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledFavoriteIconButton = styled(FavoriteIconButton)(({ theme }) => ({
|
||||
marginRight: theme.spacing(-1),
|
||||
marginBottom: theme.spacing(-1),
|
||||
margin: theme.spacing(1, 2, 0, 0),
|
||||
}));
|
||||
|
||||
export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
|
||||
children,
|
||||
const FavoriteAction: FC<{ id: string; isFavorite?: boolean }> = ({
|
||||
id,
|
||||
isFavorite = false,
|
||||
isFavorite,
|
||||
}) => {
|
||||
const { setToastApiError } = useToast();
|
||||
const { favorite, unfavorite } = useFavoriteProjectsApi();
|
||||
@ -48,14 +58,27 @@ export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
|
||||
setToastApiError('Something went wrong, could not update favorite');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
{children}
|
||||
<StyledFavoriteIconButton
|
||||
onClick={onFavorite}
|
||||
isFavorite={isFavorite}
|
||||
isFavorite={Boolean(isFavorite)}
|
||||
size='medium'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
|
||||
children,
|
||||
id,
|
||||
isFavorite = false,
|
||||
Actions = FavoriteAction,
|
||||
disabled = false,
|
||||
}) => {
|
||||
return (
|
||||
<StyledFooter disabled={disabled}>
|
||||
<StyledContainer>{children}</StyledContainer>
|
||||
<Actions id={id} isFavorite={isFavorite} />
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { VFC } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
@ -8,7 +8,7 @@ interface IProjectModeBadgeProps {
|
||||
mode: 'private' | 'protected' | 'public' | string;
|
||||
}
|
||||
|
||||
export const ProjectModeBadge: VFC<IProjectModeBadgeProps> = ({ mode }) => {
|
||||
export const ProjectModeBadge: FC<IProjectModeBadgeProps> = ({ mode }) => {
|
||||
if (mode === 'private') {
|
||||
return (
|
||||
<HtmlTooltip
|
||||
|
@ -0,0 +1,99 @@
|
||||
import { type FC, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import useProjectsArchive from 'hooks/api/getters/useProjectsArchive/useProjectsArchive';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import ApiError from 'component/common/ApiError/ApiError';
|
||||
import { styled, useMediaQuery } from '@mui/material';
|
||||
import theme from 'themes/theme';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import { ProjectGroup } from './ProjectGroup';
|
||||
import { ProjectArchiveCard } from '../NewProjectCard/ProjectArchiveCard';
|
||||
|
||||
const StyledApiError = styled(ApiError)(({ theme }) => ({
|
||||
maxWidth: '500px',
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(4),
|
||||
}));
|
||||
|
||||
type PageQueryType = Partial<Record<'search', string>>;
|
||||
|
||||
export const ArchiveProjectList: FC = () => {
|
||||
const { projects, loading, error, refetch } = useProjectsArchive();
|
||||
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [searchValue, setSearchValue] = useState(
|
||||
searchParams.get('search') || '',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const tableState: PageQueryType = {};
|
||||
if (searchValue) {
|
||||
tableState.search = searchValue;
|
||||
}
|
||||
|
||||
setSearchParams(tableState, {
|
||||
replace: true,
|
||||
});
|
||||
}, [searchValue, setSearchParams]);
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
header={
|
||||
<PageHeader
|
||||
title={`Projects archive (${projects.length || 0})`}
|
||||
actions={
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
}
|
||||
>
|
||||
<StyledContainer>
|
||||
<ConditionallyRender
|
||||
condition={error}
|
||||
show={() => (
|
||||
<StyledApiError
|
||||
onClick={refetch}
|
||||
text='Error fetching projects'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ProjectGroup
|
||||
loading={loading}
|
||||
searchValue={searchValue}
|
||||
projects={projects}
|
||||
placeholder='No archived projects found'
|
||||
ProjectCardComponent={ProjectArchiveCard}
|
||||
link={false}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ProjectCard } from '../NewProjectCard/NewProjectCard';
|
||||
@ -23,12 +24,25 @@ const StyledCardLink = styled(Link)(({ theme }) => ({
|
||||
pointer: 'cursor',
|
||||
}));
|
||||
|
||||
export const ProjectGroup: React.FC<{
|
||||
type ProjectGroupProps<T extends { id: string } = IProjectCard> = {
|
||||
sectionTitle?: string;
|
||||
projects: IProjectCard[];
|
||||
projects: T[];
|
||||
loading: boolean;
|
||||
searchValue: string;
|
||||
}> = ({ sectionTitle, projects, loading, searchValue }) => {
|
||||
placeholder?: string;
|
||||
ProjectCardComponent?: ComponentType<T & any>;
|
||||
link?: boolean;
|
||||
};
|
||||
|
||||
export const ProjectGroup = <T extends { id: string }>({
|
||||
sectionTitle,
|
||||
projects,
|
||||
loading,
|
||||
searchValue,
|
||||
placeholder = 'No projects available.',
|
||||
ProjectCardComponent = ProjectCard,
|
||||
link = true,
|
||||
}: ProjectGroupProps<T>) => {
|
||||
return (
|
||||
<article>
|
||||
<ConditionallyRender
|
||||
@ -56,9 +70,7 @@ export const ProjectGroup: React.FC<{
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No projects available.
|
||||
</TablePlaceholder>
|
||||
<TablePlaceholder>{placeholder}</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
@ -87,28 +99,24 @@ export const ProjectGroup: React.FC<{
|
||||
)}
|
||||
elseShow={() => (
|
||||
<>
|
||||
{projects.map((project: IProjectCard) => (
|
||||
{projects.map((project: T) =>
|
||||
link ? (
|
||||
<StyledCardLink
|
||||
key={project.id}
|
||||
to={`/projects/${project.id}`}
|
||||
>
|
||||
<ProjectCard
|
||||
<ProjectCardComponent
|
||||
onHover={() => {}}
|
||||
name={project.name}
|
||||
mode={project.mode}
|
||||
memberCount={
|
||||
project.memberCount ?? 0
|
||||
}
|
||||
health={project.health}
|
||||
id={project.id}
|
||||
featureCount={
|
||||
project.featureCount
|
||||
}
|
||||
isFavorite={project.favorite}
|
||||
owners={project.owners}
|
||||
{...project}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
))}
|
||||
) : (
|
||||
<ProjectCardComponent
|
||||
onHover={() => {}}
|
||||
{...project}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
@ -11,7 +11,8 @@ import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||
import Add from '@mui/icons-material/Add';
|
||||
import ApiError from 'component/common/ApiError/ApiError';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { styled, useMediaQuery } from '@mui/material';
|
||||
import { Link, styled, useMediaQuery } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import theme from 'themes/theme';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||
@ -24,6 +25,7 @@ import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
||||
import { groupProjects } from './group-projects';
|
||||
import { ProjectGroup } from './ProjectGroup';
|
||||
import { CreateProjectDialog } from '../Project/CreateProject/NewCreateProjectForm/CreateProjectDialog';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const StyledApiError = styled(ApiError)(({ theme }) => ({
|
||||
maxWidth: '500px',
|
||||
@ -38,10 +40,6 @@ const StyledContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
type PageQueryType = Partial<Record<'search', string>>;
|
||||
|
||||
type projectMap = {
|
||||
[index: string]: boolean;
|
||||
};
|
||||
|
||||
interface ICreateButtonData {
|
||||
disabled: boolean;
|
||||
tooltip?: Omit<ITooltipResolverProps, 'children'>;
|
||||
@ -128,6 +126,7 @@ export const ProjectListNew = () => {
|
||||
const [searchValue, setSearchValue] = useState(
|
||||
searchParams.get('search') || '',
|
||||
);
|
||||
const archiveProjectsEnabled = useUiFlag('archiveProjects');
|
||||
|
||||
const myProjects = new Set(useProfile().profile?.projects || []);
|
||||
|
||||
@ -201,6 +200,21 @@ export const ProjectListNew = () => {
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(archiveProjectsEnabled)}
|
||||
show={
|
||||
<>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to='/projects-archive'
|
||||
>
|
||||
Archived projects
|
||||
</Link>
|
||||
<PageHeader.Divider />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<ProjectCreationButton />
|
||||
</>
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
import type { ProjectSchema } from 'openapi';
|
||||
|
||||
// FIXME: import tpye
|
||||
interface IProjectArchiveCard {
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: string;
|
||||
archivedAt: string;
|
||||
description: string;
|
||||
featureCount: number;
|
||||
owners?: ProjectSchema['owners'];
|
||||
}
|
||||
|
||||
// TODO: implement data fetching
|
||||
const useProjectsArchive = () => {
|
||||
return {
|
||||
projects: [
|
||||
{
|
||||
name: 'Archived something',
|
||||
id: 'archi',
|
||||
createdAt: new Date('2024-08-10 16:06').toISOString(),
|
||||
archivedAt: new Date('2024-08-12 17:07').toISOString(),
|
||||
owners: [{ ownerType: 'system' }],
|
||||
},
|
||||
{
|
||||
name: 'Second example',
|
||||
id: 'pid',
|
||||
createdAt: new Date('2024-08-10 16:06').toISOString(),
|
||||
archivedAt: new Date('2024-08-12 17:07').toISOString(),
|
||||
owners: [{ ownerType: 'system' }],
|
||||
},
|
||||
],
|
||||
error: undefined as any,
|
||||
loading: false,
|
||||
refetch: () => {},
|
||||
};
|
||||
};
|
||||
|
||||
export default useProjectsArchive;
|
@ -29,6 +29,7 @@ const theme = {
|
||||
primaryHeader: '0px 8px 24px rgba(97, 91, 194, 0.2)',
|
||||
separator: '0px 2px 4px rgba(32, 32, 33, 0.12)', // Notifications header
|
||||
accordionFooter: 'inset 0px 2px 4px rgba(32, 32, 33, 0.05)',
|
||||
reverseFooter: 'inset 0px -2px 4px rgba(32, 32, 33, 0.05)',
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'Sen, Roboto, sans-serif',
|
||||
|
@ -21,6 +21,7 @@ export const theme = {
|
||||
primaryHeader: '0px 8px 24px rgba(97, 91, 194, 0.2)',
|
||||
separator: '0px 2px 4px rgba(32, 32, 33, 0.12)', // Notifications header
|
||||
accordionFooter: 'inset 0px 2px 4px rgba(32, 32, 33, 0.05)',
|
||||
reverseFooter: 'inset 0px -2px 4px rgba(32, 32, 33, 0.05)',
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'Sen, Roboto, sans-serif',
|
||||
|
@ -35,6 +35,7 @@ declare module '@mui/material/styles' {
|
||||
primaryHeader: string;
|
||||
separator: string;
|
||||
accordionFooter: string;
|
||||
reverseFooter: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user