[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 <gurusainath007@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2024-03-07 16:53:59 +05:30 committed by GitHub
parent 47a7f60611
commit 7b88a2a88c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 180 additions and 108 deletions

View File

@ -32,7 +32,7 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
...newFilters, ...newFilters,

View File

@ -31,7 +31,7 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
...newFilters, ...newFilters,

View File

@ -34,7 +34,7 @@ export const ProjectEmptyState: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { issuesFilter.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
...newFilters, ...newFilters,

View File

@ -56,7 +56,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {

View File

@ -62,7 +62,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !cycleId) return; if (!workspaceSlug || !projectId || !cycleId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters( updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -53,7 +53,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters }); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters });

View File

@ -76,7 +76,7 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
if (!workspaceSlug || !globalViewId) return; if (!workspaceSlug || !globalViewId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters( updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -61,7 +61,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !moduleId) return; if (!workspaceSlug || !projectId || !moduleId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters( updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -57,7 +57,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !userId) return; if (!workspaceSlug || !userId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { 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()); updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.FILTERS, { ...newFilters }, userId.toString());
}; };

View File

@ -59,7 +59,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters }); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters });
}; };

View File

@ -67,7 +67,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !viewId) return; if (!workspaceSlug || !projectId || !viewId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
updateFilters( updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -1,11 +1,12 @@
import { useState } from "react"; import { useMemo, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import sortBy from "lodash/sortBy";
// hooks // hooks
import { Avatar, Loader } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues";
import { useMember } from "hooks/store"; import { useMember } from "hooks/store";
// components // components
import { FilterHeader, FilterOption } from "components/issues";
// ui // ui
import { Avatar, Loader } from "@plane/ui";
type Props = { type Props = {
appliedFilters: string[] | null; appliedFilters: string[] | null;
@ -24,15 +25,23 @@ export const FilterAssignees: React.FC<Props> = observer((props: Props) => {
const appliedFiltersCount = appliedFilters?.length ?? 0; const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = memberIds?.filter( const sortedOptions = useMemo(() => {
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -44,10 +53,10 @@ export const FilterAssignees: React.FC<Props> = observer((props: Props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((memberId) => { {sortedOptions.slice(0, itemsToRender).map((memberId) => {
const member = getUserDetails(memberId); const member = getUserDetails(memberId);
if (!member) return null; if (!member) return null;
@ -61,13 +70,13 @@ export const FilterAssignees: React.FC<Props> = observer((props: Props) => {
/> />
); );
})} })}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,5 +1,6 @@
import { useState } from "react"; import { useMemo, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import sortBy from "lodash/sortBy";
// hooks // hooks
import { Avatar, Loader } from "@plane/ui"; import { Avatar, Loader } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
@ -22,16 +23,25 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
// store hooks // store hooks
const { getUserDetails } = useMember(); const { getUserDetails } = useMember();
const filteredOptions = memberIds?.filter( const sortedOptions = useMemo(() => {
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) 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 appliedFiltersCount = appliedFilters?.length ?? 0;
const handleViewToggle = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -43,10 +53,10 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((memberId) => { {sortedOptions.slice(0, itemsToRender).map((memberId) => {
const member = getUserDetails(memberId); const member = getUserDetails(memberId);
if (!member) return null; if (!member) return null;
@ -60,13 +70,13 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
/> />
); );
})} })}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import sortBy from "lodash/sortBy"; import sortBy from "lodash/sortBy";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// components // components
@ -31,16 +31,24 @@ export const FilterCycle: React.FC<Props> = observer((props) => {
const cycleIds = projectId ? getProjectCycleIds(projectId) : undefined; const cycleIds = projectId ? getProjectCycleIds(projectId) : undefined;
const cycles = cycleIds?.map((projectId) => getCycleById(projectId)!) ?? null; const cycles = cycleIds?.map((projectId) => getCycleById(projectId)!) ?? null;
const appliedFiltersCount = appliedFilters?.length ?? 0; const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = sortBy(
cycles?.filter((cycle) => cycle.name.toLowerCase().includes(searchQuery.toLowerCase())), const sortedOptions = useMemo(() => {
(cycle) => cycle.name.toLowerCase() 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups; const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups;
@ -54,10 +62,10 @@ export const FilterCycle: React.FC<Props> = observer((props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((cycle) => ( {sortedOptions.slice(0, itemsToRender).map((cycle) => (
<FilterOption <FilterOption
key={cycle.id} key={cycle.id}
isChecked={appliedFilters?.includes(cycle.id) ? true : false} isChecked={appliedFilters?.includes(cycle.id) ? true : false}
@ -69,13 +77,13 @@ export const FilterCycle: React.FC<Props> = observer((props) => {
activePulse={cycleStatus(cycle?.status) === "current" ? true : false} activePulse={cycleStatus(cycle?.status) === "current" ? true : false}
/> />
))} ))}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import sortBy from "lodash/sortBy";
// components // components
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
@ -26,13 +27,23 @@ export const FilterLabels: React.FC<Props> = observer((props) => {
const appliedFiltersCount = appliedFilters?.length ?? 0; 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -44,10 +55,10 @@ export const FilterLabels: React.FC<Props> = observer((props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((label) => ( {sortedOptions.slice(0, itemsToRender).map((label) => (
<FilterOption <FilterOption
key={label?.id} key={label?.id}
isChecked={appliedFilters?.includes(label?.id) ? true : false} isChecked={appliedFilters?.includes(label?.id) ? true : false}
@ -56,13 +67,13 @@ export const FilterLabels: React.FC<Props> = observer((props) => {
title={label.name} title={label.name}
/> />
))} ))}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,5 +1,6 @@
import { useState } from "react"; import { useMemo, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import sortBy from "lodash/sortBy";
// hooks // hooks
import { Loader, Avatar } from "@plane/ui"; import { Loader, Avatar } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
@ -24,15 +25,23 @@ export const FilterMentions: React.FC<Props> = observer((props: Props) => {
const appliedFiltersCount = appliedFilters?.length ?? 0; const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = memberIds?.filter( const sortedOptions = useMemo(() => {
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase()) 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -44,10 +53,10 @@ export const FilterMentions: React.FC<Props> = observer((props: Props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((memberId) => { {sortedOptions.slice(0, itemsToRender).map((memberId) => {
const member = getUserDetails(memberId); const member = getUserDetails(memberId);
if (!member) return null; if (!member) return null;
@ -61,13 +70,13 @@ export const FilterMentions: React.FC<Props> = observer((props: Props) => {
/> />
); );
})} })}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import sortBy from "lodash/sortBy"; import sortBy from "lodash/sortBy";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// components // components
@ -29,16 +29,24 @@ export const FilterModule: React.FC<Props> = observer((props) => {
const moduleIds = projectId ? getProjectModuleIds(projectId) : undefined; const moduleIds = projectId ? getProjectModuleIds(projectId) : undefined;
const modules = moduleIds?.map((projectId) => getModuleById(projectId)!) ?? null; const modules = moduleIds?.map((projectId) => getModuleById(projectId)!) ?? null;
const appliedFiltersCount = appliedFilters?.length ?? 0; const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = sortBy(
modules?.filter((module) => module.name.toLowerCase().includes(searchQuery.toLowerCase())), const sortedOptions = useMemo(() => {
(module) => module.name.toLowerCase() 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -50,10 +58,10 @@ export const FilterModule: React.FC<Props> = observer((props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((cycle) => ( {sortedOptions.slice(0, itemsToRender).map((cycle) => (
<FilterOption <FilterOption
key={cycle.id} key={cycle.id}
isChecked={appliedFilters?.includes(cycle.id) ? true : false} isChecked={appliedFilters?.includes(cycle.id) ? true : false}
@ -62,13 +70,13 @@ export const FilterModule: React.FC<Props> = observer((props) => {
title={cycle.name} title={cycle.name}
/> />
))} ))}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import sortBy from "lodash/sortBy";
// components // components
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
@ -26,13 +27,23 @@ export const FilterProjects: React.FC<Props> = observer((props) => {
// derived values // derived values
const projects = workspaceProjectIds?.map((projectId) => getProjectById(projectId)!) ?? null; const projects = workspaceProjectIds?.map((projectId) => getProjectById(projectId)!) ?? null;
const appliedFiltersCount = appliedFilters?.length ?? 0; 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -44,10 +55,10 @@ export const FilterProjects: React.FC<Props> = observer((props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((project) => ( {sortedOptions.slice(0, itemsToRender).map((project) => (
<FilterOption <FilterOption
key={`project-${project.id}`} key={`project-${project.id}`}
isChecked={appliedFilters?.includes(project.id) ? true : false} isChecked={appliedFilters?.includes(project.id) ? true : false}
@ -60,13 +71,13 @@ export const FilterProjects: React.FC<Props> = observer((props) => {
title={project.name} title={project.name}
/> />
))} ))}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import sortBy from "lodash/sortBy";
// components // components
import { StateGroupIcon } from "@plane/ui"; import { StateGroupIcon } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";

View File

@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import sortBy from "lodash/sortBy";
// components // components
import { Loader, StateGroupIcon } from "@plane/ui"; import { Loader, StateGroupIcon } from "@plane/ui";
import { FilterHeader, FilterOption } from "components/issues"; import { FilterHeader, FilterOption } from "components/issues";
@ -22,13 +23,18 @@ export const FilterState: React.FC<Props> = observer((props) => {
const appliedFiltersCount = appliedFilters?.length ?? 0; 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 = () => { const handleViewToggle = () => {
if (!filteredOptions) return; if (!sortedOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5); if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length); else setItemsToRender(sortedOptions.length);
}; };
return ( return (
@ -40,10 +46,10 @@ export const FilterState: React.FC<Props> = observer((props) => {
/> />
{previewEnabled && ( {previewEnabled && (
<div> <div>
{filteredOptions ? ( {sortedOptions ? (
filteredOptions.length > 0 ? ( sortedOptions.length > 0 ? (
<> <>
{filteredOptions.slice(0, itemsToRender).map((state) => ( {sortedOptions.slice(0, itemsToRender).map((state) => (
<FilterOption <FilterOption
key={state.id} key={state.id}
isChecked={appliedFilters?.includes(state.id) ? true : false} isChecked={appliedFilters?.includes(state.id) ? true : false}
@ -52,13 +58,13 @@ export const FilterState: React.FC<Props> = observer((props) => {
title={state.name} title={state.name}
/> />
))} ))}
{filteredOptions.length > 5 && ( {sortedOptions.length > 5 && (
<button <button
type="button" type="button"
className="ml-8 text-xs font-medium text-custom-primary-100" className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle} onClick={handleViewToggle}
> >
{itemsToRender === filteredOptions.length ? "View less" : "View all"} {itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button> </button>
)} )}
</> </>

View File

@ -68,7 +68,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !cycleId) return; if (!workspaceSlug || !projectId || !cycleId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
issuesFilter.updateFilters( issuesFilter.updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -59,7 +59,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !moduleId) return; if (!workspaceSlug || !projectId || !moduleId) return;
const newFilters: IIssueFilterOptions = {}; const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters ?? {}).forEach((key) => { Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null; newFilters[key as keyof IIssueFilterOptions] = [];
}); });
issuesFilter.updateFilters( issuesFilter.updateFilters(
workspaceSlug.toString(), workspaceSlug.toString(),