diff --git a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx index 3eb379b5ff..c43dfb2dda 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx @@ -11,8 +11,13 @@ import useTag from '../../hooks/use-tag' export default function TagsList() { const { t } = useTranslation() - const { tags, untaggedProjectsCount, selectedTagId, selectTag } = - useProjectListContext() + const { + tags, + projectsPerTag, + untaggedProjectsCount, + selectedTagId, + selectTag, + } = useProjectListContext() const { handleSelectTag, openCreateTagModal, @@ -34,11 +39,11 @@ export default function TagsList() { {t('new_folder')} - {sortBy(tags, tag => tag.name.toLowerCase()).map((tag, index) => { + {sortBy(tags, tag => tag.name.toLowerCase()).map(tag => { return (
  • diff --git a/services/web/frontend/js/features/project-list/context/project-list-context.tsx b/services/web/frontend/js/features/project-list/context/project-list-context.tsx index 904d21e6d5..a5eed4f409 100644 --- a/services/web/frontend/js/features/project-list/context/project-list-context.tsx +++ b/services/web/frontend/js/features/project-list/context/project-list-context.tsx @@ -29,7 +29,11 @@ import getMeta from '../../../utils/meta' import useAsync from '../../../shared/hooks/use-async' import { getProjects } from '../util/api' import sortProjects from '../util/sort-projects' -import { isDeletableProject, isLeavableProject } from '../util/project' +import { + isArchivedOrTrashed, + isDeletableProject, + isLeavableProject, +} from '../util/project' const MAX_PROJECT_PER_PAGE = 20 @@ -75,6 +79,7 @@ export type ProjectListContextValue = { setSort: React.Dispatch> tags: Tag[] untaggedProjectsCount: number + projectsPerTag: Record filter: Filter selectFilter: (filter: Filter) => void selectedTagId?: string | undefined @@ -182,8 +187,8 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) { } else { const tag = tags.find(tag => tag._id === selectedTagId) if (tag) { - filteredProjects = filteredProjects.filter(project => - tag?.project_ids?.includes(project.id) + filteredProjects = filteredProjects.filter( + p => !isArchivedOrTrashed(p) && tag?.project_ids?.includes(p.id) ) } else { setSelectedTagId(undefined) @@ -277,6 +282,15 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) { ).length }, [tags, loadedProjects]) + const projectsPerTag = useMemo(() => { + return tags.reduce>((prev, curTag) => { + const tagProjects = loadedProjects.filter(p => { + return !isArchivedOrTrashed(p) && curTag.project_ids?.includes(p.id) + }) + return { ...prev, [curTag._id]: tagProjects } + }, {}) + }, [tags, loadedProjects]) + const selectFilter = useCallback( (filter: Filter) => { setFilter(filter) @@ -432,6 +446,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) { totalProjectsCount, untaggedProjectsCount, updateProjectViewData, + projectsPerTag, visibleProjects, }), [ @@ -465,6 +480,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) { totalProjectsCount, untaggedProjectsCount, updateProjectViewData, + projectsPerTag, visibleProjects, ] ) diff --git a/services/web/frontend/js/features/project-list/util/project.ts b/services/web/frontend/js/features/project-list/util/project.ts index d005ff4be3..d40ecd757a 100644 --- a/services/web/frontend/js/features/project-list/util/project.ts +++ b/services/web/frontend/js/features/project-list/util/project.ts @@ -20,3 +20,7 @@ export function isDeletableProject(project: Project) { export function isLeavableProject(project: Project) { return project.accessLevel !== 'owner' && project.trashed } + +export function isArchivedOrTrashed(project: Project) { + return project.archived || project.trashed +} diff --git a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx index 61d5fa560a..480d0f9459 100644 --- a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx +++ b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx @@ -12,8 +12,14 @@ import { makeLongProjectList, } from '../fixtures/projects-data' -const { fullList, currentList, trashedList, leavableList, deletableList } = - makeLongProjectList(40) +const { + fullList, + currentList, + archivedList, + trashedList, + leavableList, + deletableList, +} = makeLongProjectList(40) const userId = owner.id @@ -543,6 +549,74 @@ describe('', function () { }) }) + describe('tags', function () { + it('does not show archived or trashed project', async function () { + this.unmount() + fetchMock.restore() + window.metaAttributesCache.set('ol-tags', [ + { + _id: this.tagId, + name: this.tagName, + project_ids: [ + projectsData[0].id, + projectsData[1].id, + ...archivedList.map(p => p.id), + ...trashedList.map(p => p.id), + ], + }, + ]) + + const trashProjectMock = fetchMock.post( + `express:/project/:projectId/trash`, + { status: 200 } + ) + + renderWithProjectListContext(, { + projects: fullList, + }) + + await screen.findByRole('table') + + let visibleProjectsCount = 2 + const [tagBtn] = screen.getAllByRole('button', { + name: `${this.tagName} (${visibleProjectsCount})`, + }) + fireEvent.click(tagBtn) + + const nonArchivedAndTrashedProjects = [ + projectsData[0], + projectsData[1], + ] + nonArchivedAndTrashedProjects.forEach(p => { + screen.getByText(p.name) + }) + const archivedAndTrashedProjects = [...archivedList, ...trashedList] + archivedAndTrashedProjects.forEach(p => { + expect(screen.queryByText(p.name)).to.be.null + }) + + const trashBtns = screen.getAllByRole('button', { name: 'Trash' }) + for (const [index, trashBtn] of trashBtns.entries()) { + fireEvent.click(trashBtn) + fireEvent.click(screen.getByText('Confirm')) + await waitFor(() => { + expect( + trashProjectMock.called( + `/project/${projectsData[index].id}/trash` + ) + ).to.be.true + }) + expect( + screen.queryAllByText(projectsData[index].name) + ).to.have.length(0) + + screen.getAllByRole('button', { + name: `${this.tagName} (${--visibleProjectsCount})`, + }) + } + }) + }) + describe('tags dropdown', function () { beforeEach(async function () { allCheckboxes = screen.getAllByRole('checkbox') diff --git a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts index 33299cb355..cafc192d63 100644 --- a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts +++ b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts @@ -162,6 +162,7 @@ export const makeLongProjectList = (listLength: number) => { ({ archived, trashed }) => !archived && !trashed ), trashedList: longList.filter(({ trashed }) => trashed), + archivedList: longList.filter(({ archived }) => archived), leavableList: longList.filter(isLeavableProject), deletableList: longList.filter(isDeletableProject), }