mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-08 01:15:49 +02:00
Feat/improve projects list (#8018)
This commit is contained in:
parent
8b68a0657f
commit
07abb60966
@ -1,7 +1,5 @@
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import {
|
import {
|
||||||
StyledProjectCard,
|
StyledProjectCard,
|
||||||
StyledDivHeader,
|
|
||||||
StyledCardTitle,
|
StyledCardTitle,
|
||||||
StyledProjectCardBody,
|
StyledProjectCardBody,
|
||||||
StyledIconBox,
|
StyledIconBox,
|
||||||
@ -28,11 +26,19 @@ const StyledCount = styled('strong')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInfo = styled('div')(({ theme }) => ({
|
const StyledInfo = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'grid',
|
||||||
|
gridTemplate: '1rem 1rem / 1fr 1fr',
|
||||||
|
gridAutoFlow: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
alignItems: 'flex-end',
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('div')(({ theme }) => ({
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ProjectCard = ({
|
export const ProjectCard = ({
|
||||||
@ -45,6 +51,7 @@ export const ProjectCard = ({
|
|||||||
mode,
|
mode,
|
||||||
favorite = false,
|
favorite = false,
|
||||||
owners,
|
owners,
|
||||||
|
createdAt,
|
||||||
lastUpdatedAt,
|
lastUpdatedAt,
|
||||||
lastReportedFlagUsage,
|
lastReportedFlagUsage,
|
||||||
}: IProjectCard) => {
|
}: IProjectCard) => {
|
||||||
@ -53,7 +60,7 @@ export const ProjectCard = ({
|
|||||||
return (
|
return (
|
||||||
<StyledProjectCard onMouseEnter={onHover}>
|
<StyledProjectCard onMouseEnter={onHover}>
|
||||||
<StyledProjectCardBody>
|
<StyledProjectCardBody>
|
||||||
<StyledDivHeader>
|
<StyledHeader>
|
||||||
<StyledIconBox>
|
<StyledIconBox>
|
||||||
<ProjectIcon />
|
<ProjectIcon />
|
||||||
</StyledIconBox>
|
</StyledIconBox>
|
||||||
@ -69,29 +76,26 @@ export const ProjectCard = ({
|
|||||||
{name}
|
{name}
|
||||||
</Highlighter>
|
</Highlighter>
|
||||||
</StyledCardTitle>
|
</StyledCardTitle>
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(lastUpdatedAt)}
|
|
||||||
show={
|
|
||||||
<StyledUpdated>
|
<StyledUpdated>
|
||||||
Updated <TimeAgo date={lastUpdatedAt} />
|
Updated{' '}
|
||||||
|
<TimeAgo date={lastUpdatedAt || createdAt} />
|
||||||
</StyledUpdated>
|
</StyledUpdated>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<ProjectModeBadge mode={mode} />
|
<ProjectModeBadge mode={mode} />
|
||||||
<FavoriteAction id={id} isFavorite={favorite} />
|
<FavoriteAction id={id} isFavorite={favorite} />
|
||||||
</StyledDivHeader>
|
</StyledHeader>
|
||||||
<StyledInfo>
|
<StyledInfo>
|
||||||
<div>
|
<div data-loading>
|
||||||
<div>
|
|
||||||
<StyledCount>{featureCount}</StyledCount> flag
|
<StyledCount>{featureCount}</StyledCount> flag
|
||||||
{featureCount === 1 ? '' : 's'}
|
{featureCount === 1 ? '' : 's'}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div data-loading>
|
||||||
<StyledCount>{health}%</StyledCount> health
|
<StyledCount>{health}%</StyledCount> health
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div />
|
||||||
|
<div data-loading>
|
||||||
<ProjectLastSeen date={lastReportedFlagUsage} />
|
<ProjectLastSeen date={lastReportedFlagUsage} />
|
||||||
|
</div>
|
||||||
</StyledInfo>
|
</StyledInfo>
|
||||||
</StyledProjectCardBody>
|
</StyledProjectCardBody>
|
||||||
<ProjectCardFooter
|
<ProjectCardFooter
|
||||||
|
@ -60,6 +60,7 @@ const StyledUserName = styled('span')(({ theme }) => ({
|
|||||||
const StyledContainer = styled('div')(() => ({
|
const StyledContainer = styled('div')(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
borderRadius: '50%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledOwnerName = styled('div')(({ theme }) => ({
|
const StyledOwnerName = styled('div')(({ theme }) => ({
|
||||||
@ -74,6 +75,7 @@ const StyledHeader = styled('span')(({ theme }) => ({
|
|||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
fontWeight: theme.typography.fontWeightRegular,
|
||||||
|
marginRight: 'auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledWrapper = styled('div')(({ theme }) => ({
|
const StyledWrapper = styled('div')(({ theme }) => ({
|
||||||
@ -92,10 +94,10 @@ export const ProjectOwners: FC<IProjectOwnersProps> = ({ owners = [] }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper data-testid='test'>
|
<StyledWrapper data-testid='test'>
|
||||||
<StyledContainer>
|
<StyledContainer data-loading>
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
users={users}
|
users={users}
|
||||||
avatarLimit={4}
|
avatarLimit={6}
|
||||||
AvatarComponent={StyledAvatarComponent}
|
AvatarComponent={StyledAvatarComponent}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
@ -103,8 +105,10 @@ export const ProjectOwners: FC<IProjectOwnersProps> = ({ owners = [] }) => {
|
|||||||
condition={owners.length === 1}
|
condition={owners.length === 1}
|
||||||
show={
|
show={
|
||||||
<StyledOwnerName>
|
<StyledOwnerName>
|
||||||
<StyledHeader>Owner</StyledHeader>
|
<StyledHeader data-loading>Owner</StyledHeader>
|
||||||
<StyledUserName>{users[0]?.name}</StyledUserName>
|
<StyledUserName data-loading>
|
||||||
|
{users[0]?.name}
|
||||||
|
</StyledUserName>
|
||||||
</StyledOwnerName>
|
</StyledOwnerName>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
Tooltip,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
import ArchiveIcon from '@mui/icons-material/Inventory2Outlined';
|
||||||
|
import { Link as RouterLink, useNavigate } from 'react-router-dom';
|
||||||
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export const ProjectArchiveLink: FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return (
|
||||||
|
<Tooltip arrow title='See archived projects'>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => navigate('/projects-archive')}
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
<ArchiveIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link component={RouterLink} to='/projects-archive' data-loading>
|
||||||
|
Archived projects
|
||||||
|
</Link>
|
||||||
|
<PageHeader.Divider />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import type { ComponentType } from 'react';
|
import type { ComponentType, ReactNode } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard';
|
import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard';
|
||||||
@ -10,6 +10,26 @@ import { TablePlaceholder } from 'component/common/Table';
|
|||||||
import { styled, Typography } from '@mui/material';
|
import { styled, Typography } from '@mui/material';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
|
import { flexColumn } from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledContainer = styled('article')(({ theme }) => ({
|
||||||
|
...flexColumn,
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column-reverse',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderTitle = styled('div')(() => ({
|
||||||
|
flexGrow: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledGridContainer = styled('div')(({ theme }) => ({
|
const StyledGridContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -29,6 +49,8 @@ const StyledCardLink = styled(Link)(({ theme }) => ({
|
|||||||
|
|
||||||
type ProjectGroupProps = {
|
type ProjectGroupProps = {
|
||||||
sectionTitle?: string;
|
sectionTitle?: string;
|
||||||
|
sectionSubtitle?: string;
|
||||||
|
HeaderActions?: ReactNode;
|
||||||
projects: IProjectCard[];
|
projects: IProjectCard[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
/**
|
/**
|
||||||
@ -42,6 +64,8 @@ type ProjectGroupProps = {
|
|||||||
|
|
||||||
export const ProjectGroup = ({
|
export const ProjectGroup = ({
|
||||||
sectionTitle,
|
sectionTitle,
|
||||||
|
sectionSubtitle,
|
||||||
|
HeaderActions,
|
||||||
projects,
|
projects,
|
||||||
loading,
|
loading,
|
||||||
searchValue,
|
searchValue,
|
||||||
@ -56,19 +80,31 @@ export const ProjectGroup = ({
|
|||||||
const { searchQuery } = useSearchHighlightContext();
|
const { searchQuery } = useSearchHighlightContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article>
|
<StyledContainer>
|
||||||
|
<StyledHeaderContainer>
|
||||||
|
<StyledHeaderTitle>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(sectionTitle)}
|
condition={Boolean(sectionTitle)}
|
||||||
show={
|
show={
|
||||||
<Typography
|
<Typography component='h2' variant='h2'>
|
||||||
component='h2'
|
|
||||||
variant='h3'
|
|
||||||
sx={(theme) => ({ marginBottom: theme.spacing(2) })}
|
|
||||||
>
|
|
||||||
{sectionTitle}
|
{sectionTitle}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
Boolean(sectionSubtitle) &&
|
||||||
|
projectListImprovementsEnabled
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<Typography variant='body2' color='text.secondary'>
|
||||||
|
{sectionSubtitle}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledHeaderTitle>
|
||||||
|
{HeaderActions}
|
||||||
|
</StyledHeaderContainer>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={projects.length < 1 && !loading}
|
condition={projects.length < 1 && !loading}
|
||||||
show={
|
show={
|
||||||
@ -104,6 +140,12 @@ export const ProjectGroup = ({
|
|||||||
memberCount={2}
|
memberCount={2}
|
||||||
health={95}
|
health={95}
|
||||||
featureCount={4}
|
featureCount={4}
|
||||||
|
owners={[
|
||||||
|
{
|
||||||
|
ownerType: 'user',
|
||||||
|
name: 'Loading data',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@ -132,6 +174,6 @@ export const ProjectGroup = ({
|
|||||||
</StyledGridContainer>
|
</StyledGridContainer>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</article>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,8 +4,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import ApiError from 'component/common/ApiError/ApiError';
|
import ApiError from 'component/common/ApiError/ApiError';
|
||||||
import { Link, styled, useMediaQuery } from '@mui/material';
|
import { styled, useMediaQuery } from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { Search } from 'component/common/Search/Search';
|
import { Search } from 'component/common/Search/Search';
|
||||||
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
||||||
@ -18,6 +17,7 @@ import { ProjectList as LegacyProjectList } from './LegacyProjectList';
|
|||||||
import { ProjectCreationButton } from './ProjectCreationButton/ProjectCreationButton';
|
import { ProjectCreationButton } from './ProjectCreationButton/ProjectCreationButton';
|
||||||
import { useGroupedProjects } from './hooks/useGroupedProjects';
|
import { useGroupedProjects } from './hooks/useGroupedProjects';
|
||||||
import { useProjectsSearchAndSort } from './hooks/useProjectsSearchAndSort';
|
import { useProjectsSearchAndSort } from './hooks/useProjectsSearchAndSort';
|
||||||
|
import { ProjectArchiveLink } from './ProjectArchiveLink/ProjectArchiveLink';
|
||||||
|
|
||||||
const StyledApiError = styled(ApiError)(({ theme }) => ({
|
const StyledApiError = styled(ApiError)(({ theme }) => ({
|
||||||
maxWidth: '500px',
|
maxWidth: '500px',
|
||||||
@ -27,7 +27,7 @@ const StyledApiError = styled(ApiError)(({ theme }) => ({
|
|||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(4),
|
gap: theme.spacing(6),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const NewProjectList = () => {
|
const NewProjectList = () => {
|
||||||
@ -77,19 +77,10 @@ const NewProjectList = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(archiveProjectsEnabled)}
|
condition={Boolean(archiveProjectsEnabled)}
|
||||||
show={
|
show={<ProjectArchiveLink />}
|
||||||
<>
|
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to='/projects-archive'
|
|
||||||
>
|
|
||||||
Archived projects
|
|
||||||
</Link>
|
|
||||||
<PageHeader.Divider />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ProjectCreationButton
|
<ProjectCreationButton
|
||||||
isDialogOpen={Boolean(state.create)}
|
isDialogOpen={Boolean(state.create)}
|
||||||
@ -124,21 +115,27 @@ const NewProjectList = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<ProjectsListSort
|
|
||||||
sortBy={state.sortBy}
|
|
||||||
setSortBy={(sortBy) =>
|
|
||||||
setState({ sortBy: sortBy as typeof state.sortBy })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<SearchHighlightProvider value={state.query || ''}>
|
<SearchHighlightProvider value={state.query || ''}>
|
||||||
<ProjectGroup
|
<ProjectGroup
|
||||||
sectionTitle='My projects'
|
sectionTitle='My projects'
|
||||||
|
sectionSubtitle='Favorite projects, projects you own or projects you are a member of.'
|
||||||
|
HeaderActions={
|
||||||
|
<ProjectsListSort
|
||||||
|
sortBy={state.sortBy}
|
||||||
|
setSortBy={(sortBy) =>
|
||||||
|
setState({
|
||||||
|
sortBy: sortBy as typeof state.sortBy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
projects={groupedProjects.myProjects}
|
projects={groupedProjects.myProjects}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProjectGroup
|
<ProjectGroup
|
||||||
sectionTitle='Other projects'
|
sectionTitle='Other projects'
|
||||||
|
sectionSubtitle='Projects in Unleash that you have access to.'
|
||||||
loading={loading}
|
loading={loading}
|
||||||
projects={groupedProjects.otherProjects}
|
projects={groupedProjects.otherProjects}
|
||||||
/>
|
/>
|
||||||
|
@ -5,7 +5,7 @@ import { styled } from '@mui/material';
|
|||||||
const StyledWrapper = styled('div')(({ theme }) => ({
|
const StyledWrapper = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
margin: theme.spacing(0, 0, -4, 0),
|
flex: 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledContainer = styled('div')(() => ({
|
const StyledContainer = styled('div')(() => ({
|
||||||
@ -21,7 +21,7 @@ const options: Array<{
|
|||||||
}> = [
|
}> = [
|
||||||
{ key: 'name', label: 'Project name' },
|
{ key: 'name', label: 'Project name' },
|
||||||
{ key: 'created', label: 'Recently created' },
|
{ key: 'created', label: 'Recently created' },
|
||||||
{ key: 'updated', label: 'Recently updated' },
|
{ key: 'updated', label: 'Last updated' },
|
||||||
{ key: 'seen', label: 'Last usage reported' },
|
{ key: 'seen', label: 'Last usage reported' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -219,4 +219,35 @@ describe('useProjectsSearchAndSort', () => {
|
|||||||
'Project A',
|
'Project A',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use createdAt if lastUpdatedAt is not available', () => {
|
||||||
|
const { result } = renderHook(
|
||||||
|
(sortBy: string) =>
|
||||||
|
useProjectsSearchAndSort(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'Project A',
|
||||||
|
id: '1',
|
||||||
|
createdAt: '2024-01-01',
|
||||||
|
lastUpdatedAt: '2024-01-02',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project B',
|
||||||
|
id: '2',
|
||||||
|
createdAt: '2024-02-01',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
sortBy as any,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
initialProps: 'updated',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.map((project) => project.name)).toEqual([
|
||||||
|
'Project B',
|
||||||
|
'Project A',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,11 @@ export const useProjectsSearchAndSort = (
|
|||||||
? projects.filter((project) => regExp.test(project.name))
|
? projects.filter((project) => regExp.test(project.name))
|
||||||
: projects
|
: projects
|
||||||
)
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aVal = `${a.name || ''}`.toLowerCase();
|
||||||
|
const bVal = `${b.name || ''}`.toLowerCase();
|
||||||
|
return aVal?.localeCompare(bVal);
|
||||||
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (sortBy === 'created') {
|
if (sortBy === 'created') {
|
||||||
const aVal = new Date(a.createdAt || 0);
|
const aVal = new Date(a.createdAt || 0);
|
||||||
@ -23,8 +28,8 @@ export const useProjectsSearchAndSort = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy === 'updated') {
|
if (sortBy === 'updated') {
|
||||||
const aVal = new Date(a.lastUpdatedAt || 0);
|
const aVal = new Date(a.lastUpdatedAt || a.createdAt || 0);
|
||||||
const bVal = new Date(b.lastUpdatedAt || 0);
|
const bVal = new Date(b.lastUpdatedAt || b.createdAt || 0);
|
||||||
return bVal?.getTime() - aVal?.getTime();
|
return bVal?.getTime() - aVal?.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +39,7 @@ export const useProjectsSearchAndSort = (
|
|||||||
return bVal?.getTime() - aVal?.getTime();
|
return bVal?.getTime() - aVal?.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
const aVal = `${a.name || ''}`.toLowerCase();
|
return 0;
|
||||||
const bVal = `${b.name || ''}`.toLowerCase();
|
|
||||||
return aVal?.localeCompare(bVal);
|
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a?.favorite && !b?.favorite) {
|
if (a?.favorite && !b?.favorite) {
|
||||||
|
Loading…
Reference in New Issue
Block a user