From 7b88a2a88cc777be716a3afdd80bc82ea791bad5 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:53:59 +0530 Subject: [PATCH] [WEB-469] chore: selected filter sorting added in filter dropdown (#3869) * chore: selected filter sorting added in filter dropdown * chore: handleClearAllFilters function updated * chore: filter dropdown sorting updated * chore: filter dropdown sorting updated * chore: filter dropdown sorting updated --------- Co-authored-by: gurusainath --- .../empty-states/archived-issues.tsx | 2 +- .../empty-states/draft-issues.tsx | 2 +- .../empty-states/project-issues.tsx | 2 +- .../applied-filters/roots/archived-issue.tsx | 2 +- .../applied-filters/roots/cycle-root.tsx | 2 +- .../applied-filters/roots/draft-issue.tsx | 2 +- .../roots/global-view-root.tsx | 2 +- .../applied-filters/roots/module-root.tsx | 2 +- .../roots/profile-issues-root.tsx | 2 +- .../applied-filters/roots/project-root.tsx | 2 +- .../roots/project-view-root.tsx | 2 +- .../filters/header/filters/assignee.tsx | 37 ++++++++++++------- .../filters/header/filters/created-by.tsx | 34 +++++++++++------ .../filters/header/filters/cycle.tsx | 34 ++++++++++------- .../filters/header/filters/labels.tsx | 31 +++++++++++----- .../filters/header/filters/mentions.tsx | 33 +++++++++++------ .../filters/header/filters/module.tsx | 34 ++++++++++------- .../filters/header/filters/project.tsx | 31 +++++++++++----- .../filters/header/filters/state-group.tsx | 2 +- .../filters/header/filters/state.tsx | 26 ++++++++----- .../issue-layouts/roots/cycle-layout-root.tsx | 2 +- .../roots/module-layout-root.tsx | 2 +- 22 files changed, 180 insertions(+), 108 deletions(-) diff --git a/web/components/issues/issue-layouts/empty-states/archived-issues.tsx b/web/components/issues/issue-layouts/empty-states/archived-issues.tsx index c9de2279c..36c895de3 100644 --- a/web/components/issues/issue-layouts/empty-states/archived-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/archived-issues.tsx @@ -32,7 +32,7 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => { if (!workspaceSlug || !projectId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters, diff --git a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx index 0968ed07a..c23fea100 100644 --- a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx @@ -31,7 +31,7 @@ export const ProjectDraftEmptyState: React.FC = observer(() => { if (!workspaceSlug || !projectId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters, diff --git a/web/components/issues/issue-layouts/empty-states/project-issues.tsx b/web/components/issues/issue-layouts/empty-states/project-issues.tsx index 12642d364..58929e48d 100644 --- a/web/components/issues/issue-layouts/empty-states/project-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/project-issues.tsx @@ -34,7 +34,7 @@ export const ProjectEmptyState: React.FC = observer(() => { if (!workspaceSlug || !projectId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters, diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx index 35651d870..7e6926fa5 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx @@ -56,7 +56,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => { const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx index 6a741b73d..ee6b4e694 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx @@ -62,7 +62,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !cycleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters( workspaceSlug.toString(), diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx index a075d59d2..61c0e346a 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx @@ -53,7 +53,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => { const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index a431652f1..d907cf168 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -76,7 +76,7 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { if (!workspaceSlug || !globalViewId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters( workspaceSlug.toString(), diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx index b49ddf4d6..fc78d79ea 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx @@ -61,7 +61,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !moduleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters( workspaceSlug.toString(), diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/profile-issues-root.tsx index 91eeef423..b0c496a7b 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/profile-issues-root.tsx @@ -57,7 +57,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !userId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.FILTERS, { ...newFilters }, userId.toString()); }; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx index c0b67043a..602f3fa2d 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx @@ -59,7 +59,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters }); }; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 760d2e7e4..6586159fa 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -67,7 +67,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !viewId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); updateFilters( workspaceSlug.toString(), diff --git a/web/components/issues/issue-layouts/filters/header/filters/assignee.tsx b/web/components/issues/issue-layouts/filters/header/filters/assignee.tsx index b26b688af..c51fcf7ab 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/assignee.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/assignee.tsx @@ -1,11 +1,12 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { observer } from "mobx-react-lite"; +import sortBy from "lodash/sortBy"; // hooks -import { Avatar, Loader } from "@plane/ui"; -import { FilterHeader, FilterOption } from "components/issues"; import { useMember } from "hooks/store"; // components +import { FilterHeader, FilterOption } from "components/issues"; // ui +import { Avatar, Loader } from "@plane/ui"; type Props = { appliedFilters: string[] | null; @@ -24,15 +25,23 @@ export const FilterAssignees: React.FC = observer((props: Props) => { const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = memberIds?.filter( - (memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const sortedOptions = useMemo(() => { + const filteredOptions = (memberIds || []).filter((memberId) => + getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (memberId) => !(appliedFilters ?? []).includes(memberId), + (memberId) => getUserDetails(memberId)?.display_name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -44,10 +53,10 @@ export const FilterAssignees: React.FC = observer((props: Props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((memberId) => { + {sortedOptions.slice(0, itemsToRender).map((memberId) => { const member = getUserDetails(memberId); if (!member) return null; @@ -61,13 +70,13 @@ export const FilterAssignees: React.FC = observer((props: Props) => { /> ); })} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/created-by.tsx b/web/components/issues/issue-layouts/filters/header/filters/created-by.tsx index 45e3309a9..765955bf9 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/created-by.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/created-by.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { observer } from "mobx-react-lite"; +import sortBy from "lodash/sortBy"; // hooks import { Avatar, Loader } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; @@ -22,16 +23,25 @@ export const FilterCreatedBy: React.FC = observer((props: Props) => { // store hooks const { getUserDetails } = useMember(); - const filteredOptions = memberIds?.filter( - (memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const sortedOptions = useMemo(() => { + const filteredOptions = (memberIds || []).filter((memberId) => + getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (memberId) => !(appliedFilters ?? []).includes(memberId), + (memberId) => getUserDetails(memberId)?.display_name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); + const appliedFiltersCount = appliedFilters?.length ?? 0; const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -43,10 +53,10 @@ export const FilterCreatedBy: React.FC = observer((props: Props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((memberId) => { + {sortedOptions.slice(0, itemsToRender).map((memberId) => { const member = getUserDetails(memberId); if (!member) return null; @@ -60,13 +70,13 @@ export const FilterCreatedBy: React.FC = observer((props: Props) => { /> ); })} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx b/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx index 396addde6..b3a65a399 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import sortBy from "lodash/sortBy"; import { observer } from "mobx-react"; // components @@ -31,16 +31,24 @@ export const FilterCycle: React.FC = observer((props) => { const cycleIds = projectId ? getProjectCycleIds(projectId) : undefined; const cycles = cycleIds?.map((projectId) => getCycleById(projectId)!) ?? null; const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = sortBy( - cycles?.filter((cycle) => cycle.name.toLowerCase().includes(searchQuery.toLowerCase())), - (cycle) => cycle.name.toLowerCase() - ); + + const sortedOptions = useMemo(() => { + const filteredOptions = (cycles || []).filter((cycle) => + cycle.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (cycle) => !appliedFilters?.includes(cycle.id), + (cycle) => cycle.name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups; @@ -54,10 +62,10 @@ export const FilterCycle: React.FC = observer((props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((cycle) => ( + {sortedOptions.slice(0, itemsToRender).map((cycle) => ( = observer((props) => { activePulse={cycleStatus(cycle?.status) === "current" ? true : false} /> ))} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/labels.tsx b/web/components/issues/issue-layouts/filters/header/filters/labels.tsx index 42e955535..7097b1337 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/labels.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/labels.tsx @@ -1,5 +1,6 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { observer } from "mobx-react"; +import sortBy from "lodash/sortBy"; // components import { Loader } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; @@ -26,13 +27,23 @@ export const FilterLabels: React.FC = observer((props) => { const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = labels?.filter((label) => label.name.toLowerCase().includes(searchQuery.toLowerCase())); + const sortedOptions = useMemo(() => { + const filteredOptions = (labels || []).filter((label) => + label.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (label) => !(appliedFilters ?? []).includes(label.id), + (label) => label.name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -44,10 +55,10 @@ export const FilterLabels: React.FC = observer((props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((label) => ( + {sortedOptions.slice(0, itemsToRender).map((label) => ( = observer((props) => { title={label.name} /> ))} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/mentions.tsx b/web/components/issues/issue-layouts/filters/header/filters/mentions.tsx index 4d2839b2c..80c16478a 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/mentions.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/mentions.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { observer } from "mobx-react-lite"; +import sortBy from "lodash/sortBy"; // hooks import { Loader, Avatar } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; @@ -24,15 +25,23 @@ export const FilterMentions: React.FC = observer((props: Props) => { const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = memberIds?.filter( - (memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const sortedOptions = useMemo(() => { + const filteredOptions = (memberIds || []).filter((memberId) => + getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (memberId) => !(appliedFilters ?? []).includes(memberId), + (memberId) => getUserDetails(memberId)?.display_name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -44,10 +53,10 @@ export const FilterMentions: React.FC = observer((props: Props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((memberId) => { + {sortedOptions.slice(0, itemsToRender).map((memberId) => { const member = getUserDetails(memberId); if (!member) return null; @@ -61,13 +70,13 @@ export const FilterMentions: React.FC = observer((props: Props) => { /> ); })} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/module.tsx b/web/components/issues/issue-layouts/filters/header/filters/module.tsx index 812cf939f..6b6cd2b4d 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/module.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/module.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import sortBy from "lodash/sortBy"; import { observer } from "mobx-react"; // components @@ -29,16 +29,24 @@ export const FilterModule: React.FC = observer((props) => { const moduleIds = projectId ? getProjectModuleIds(projectId) : undefined; const modules = moduleIds?.map((projectId) => getModuleById(projectId)!) ?? null; const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = sortBy( - modules?.filter((module) => module.name.toLowerCase().includes(searchQuery.toLowerCase())), - (module) => module.name.toLowerCase() - ); + + const sortedOptions = useMemo(() => { + const filteredOptions = (modules || []).filter((module) => + module.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return sortBy(filteredOptions, [ + (module) => !appliedFilters?.includes(module.id), + (module) => module.name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -50,10 +58,10 @@ export const FilterModule: React.FC = observer((props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((cycle) => ( + {sortedOptions.slice(0, itemsToRender).map((cycle) => ( = observer((props) => { title={cycle.name} /> ))} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/project.tsx b/web/components/issues/issue-layouts/filters/header/filters/project.tsx index b9f864b4b..b97001b00 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/project.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/project.tsx @@ -1,5 +1,6 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { observer } from "mobx-react"; +import sortBy from "lodash/sortBy"; // components import { Loader } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; @@ -26,13 +27,23 @@ export const FilterProjects: React.FC = observer((props) => { // derived values const projects = workspaceProjectIds?.map((projectId) => getProjectById(projectId)!) ?? null; const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = projects?.filter((project) => project.name.toLowerCase().includes(searchQuery.toLowerCase())); + + const sortedOptions = useMemo(() => { + const filteredOptions = (projects || []).filter((project) => + project.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + return sortBy(filteredOptions, [ + (project) => !(appliedFilters ?? []).includes(project.id), + (project) => project.name.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -44,10 +55,10 @@ export const FilterProjects: React.FC = observer((props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((project) => ( + {sortedOptions.slice(0, itemsToRender).map((project) => ( = observer((props) => { title={project.name} /> ))} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/filters/header/filters/state-group.tsx b/web/components/issues/issue-layouts/filters/header/filters/state-group.tsx index 06c1aae9f..e283112be 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/state-group.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/state-group.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; - +import sortBy from "lodash/sortBy"; // components import { StateGroupIcon } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; diff --git a/web/components/issues/issue-layouts/filters/header/filters/state.tsx b/web/components/issues/issue-layouts/filters/header/filters/state.tsx index 5dde1d279..2c2cca53b 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/state.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/state.tsx @@ -1,5 +1,6 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { observer } from "mobx-react"; +import sortBy from "lodash/sortBy"; // components import { Loader, StateGroupIcon } from "@plane/ui"; import { FilterHeader, FilterOption } from "components/issues"; @@ -22,13 +23,18 @@ export const FilterState: React.FC = observer((props) => { const appliedFiltersCount = appliedFilters?.length ?? 0; - const filteredOptions = states?.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase())); + const sortedOptions = useMemo(() => { + const filteredOptions = (states ?? []).filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase())); + + return sortBy(filteredOptions, [(s) => !(appliedFilters ?? []).includes(s.id), (s) => s.name.toLowerCase()]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchQuery]); const handleViewToggle = () => { - if (!filteredOptions) return; + if (!sortedOptions) return; - if (itemsToRender === filteredOptions.length) setItemsToRender(5); - else setItemsToRender(filteredOptions.length); + if (itemsToRender === sortedOptions.length) setItemsToRender(5); + else setItemsToRender(sortedOptions.length); }; return ( @@ -40,10 +46,10 @@ export const FilterState: React.FC = observer((props) => { /> {previewEnabled && (
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( + {sortedOptions ? ( + sortedOptions.length > 0 ? ( <> - {filteredOptions.slice(0, itemsToRender).map((state) => ( + {sortedOptions.slice(0, itemsToRender).map((state) => ( = observer((props) => { title={state.name} /> ))} - {filteredOptions.length > 5 && ( + {sortedOptions.length > 5 && ( )} diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index 5f308fbd1..ce0a9943e 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -68,7 +68,7 @@ export const CycleLayoutRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !cycleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); issuesFilter.updateFilters( workspaceSlug.toString(), diff --git a/web/components/issues/issue-layouts/roots/module-layout-root.tsx b/web/components/issues/issue-layouts/roots/module-layout-root.tsx index 0c6ba3b66..268a2c60c 100644 --- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx @@ -59,7 +59,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !moduleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { - newFilters[key as keyof IIssueFilterOptions] = null; + newFilters[key as keyof IIssueFilterOptions] = []; }); issuesFilter.updateFilters( workspaceSlug.toString(),