[WEB-1176] chore: filter dropdown indicator and code refactor (#4379)

* chore: filter dropdown indicator and code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: refactor calculateTotalFilters function with typescript generics
This commit is contained in:
Anmol Singh Bhatia 2024-05-07 14:56:19 +05:30 committed by GitHub
parent 1eba6c24cd
commit 2aef40b7c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 219 additions and 87 deletions

View File

@ -11,6 +11,7 @@ import { CycleFiltersSelection } from "@/components/cycles";
import { FiltersDropdown } from "@/components/issues"; import { FiltersDropdown } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useCycleFilter } from "@/hooks/store"; import { useCycleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -61,6 +62,8 @@ export const ArchivedCyclesHeader: FC = observer(() => {
} }
}; };
const isFiltersApplied = calculateTotalFilters(currentProjectArchivedFilters ?? {}) !== 0;
return ( return (
<div className="group relative flex border-b border-custom-border-200"> <div className="group relative flex border-b border-custom-border-200">
<div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm"> <div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm">
@ -110,7 +113,12 @@ export const ArchivedCyclesHeader: FC = observer(() => {
</button> </button>
)} )}
</div> </div>
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<CycleFiltersSelection <CycleFiltersSelection
filters={currentProjectArchivedFilters ?? {}} filters={currentProjectArchivedFilters ?? {}}
handleFiltersUpdate={handleFilters} handleFiltersUpdate={handleFilters}

View File

@ -1,15 +1,19 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import router from "next/router"; import router from "next/router";
//components
// icons // icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { CustomMenu } from "@plane/ui"; import { CustomMenu } from "@plane/ui";
// hooks // components
// constants
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store"; import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
export const CycleMobileHeader = () => { export const CycleMobileHeader = () => {
@ -103,6 +107,8 @@ export const CycleMobileHeader = () => {
[workspaceSlug, projectId, cycleId, updateFilters] [workspaceSlug, projectId, cycleId, updateFilters]
); );
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -142,6 +148,7 @@ export const CycleMobileHeader = () => {
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" /> <ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span> </span>
} }
isFiltersApplied={isFiltersApplied}
> >
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}

View File

@ -1,6 +1,8 @@
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// icons
import { ListFilter, Search, X } from "lucide-react"; import { ListFilter, Search, X } from "lucide-react";
// headless ui
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
// types // types
import { TCycleFilters } from "@plane/types"; import { TCycleFilters } from "@plane/types";
@ -13,6 +15,7 @@ import { FiltersDropdown } from "@/components/issues";
import { CYCLE_TABS_LIST, CYCLE_VIEW_LAYOUTS } from "@/constants/cycle"; import { CYCLE_TABS_LIST, CYCLE_VIEW_LAYOUTS } from "@/constants/cycle";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useCycleFilter } from "@/hooks/store"; import { useCycleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -75,6 +78,8 @@ export const CyclesViewHeader: React.FC<Props> = observer((props) => {
} }
}; };
const isFiltersApplied = calculateTotalFilters(currentProjectFilters ?? {}) !== 0;
return ( return (
<div className="h-[50px] flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 border-b border-custom-border-200 px-6 sm:pb-0"> <div className="h-[50px] flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 border-b border-custom-border-200 px-6 sm:pb-0">
<Tab.List as="div" className="flex items-center overflow-x-scroll"> <Tab.List as="div" className="flex items-center overflow-x-scroll">
@ -135,7 +140,12 @@ export const CyclesViewHeader: React.FC<Props> = observer((props) => {
</button> </button>
)} )}
</div> </div>
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<CycleFiltersSelection filters={currentProjectFilters ?? {}} handleFiltersUpdate={handleFilters} /> <CycleFiltersSelection filters={currentProjectFilters ?? {}} handleFiltersUpdate={handleFilters} />
</FiltersDropdown> </FiltersDropdown>
<div className="flex items-center gap-1 rounded bg-custom-background-80 p-1"> <div className="flex items-center gap-1 rounded bg-custom-background-80 p-1">

View File

@ -2,19 +2,25 @@ import { useCallback, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // icons
// components
import { ArrowRight, Plus, PanelRight } from "lucide-react"; import { ArrowRight, Plus, PanelRight } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui"; import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project"; import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper"; import { truncateText } from "@/helpers/string.helper";
// hooks
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -27,12 +33,7 @@ import {
useIssues, useIssues,
} from "@/hooks/store"; } from "@/hooks/store";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
// ui
// icons
// helpers
// types
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
// constants
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
// router // router
@ -152,6 +153,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
: cycleDetails.total_issues : cycleDetails.total_issues
: undefined; : undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -239,7 +242,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -14,6 +14,8 @@ import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
// constants // constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserWorkspaceRoles } from "@/constants/workspace"; import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useLabel, useMember, useUser, useIssues } from "@/hooks/store"; import { useLabel, useMember, useUser, useIssues } from "@/hooks/store";
@ -94,6 +96,8 @@ export const GlobalIssuesHeader: React.FC = observer(() => {
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} /> <CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
@ -110,7 +114,7 @@ export const GlobalIssuesHeader: React.FC = observer(() => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<> <>
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet} layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}

View File

@ -2,18 +2,25 @@ import { useCallback, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // icons
import { ArrowRight, PanelRight, Plus } from "lucide-react"; import { ArrowRight, PanelRight, Plus } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui"; import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project"; import { ProjectLogo } from "@/components/project";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper"; import { truncateText } from "@/helpers/string.helper";
// hooks
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -27,13 +34,7 @@ import {
} from "@/hooks/store"; } from "@/hooks/store";
import { useIssuesActions } from "@/hooks/use-issues-actions"; import { useIssuesActions } from "@/hooks/use-issues-actions";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
// components
// ui
// icons
// helpers
// types
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
// constants
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
// router // router
@ -152,6 +153,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
: moduleDetails.total_issues : moduleDetails.total_issues
: undefined; : undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -240,7 +243,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,17 +1,19 @@
import { FC, useCallback } from "react"; import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// hooks
// components
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// ui // ui
// helper import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project"; import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
@ -83,6 +85,8 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
: currentProjectDetails.draft_issues : currentProjectDetails.draft_issues
: undefined; : undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4"> <div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
@ -131,7 +135,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,16 +1,23 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// icons
import { Briefcase, Circle, ExternalLink, Plus } from "lucide-react"; import { Briefcase, Circle, ExternalLink, Plus } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// hooks // ui
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui"; import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project"; import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -21,12 +28,7 @@ import {
useMember, useMember,
} from "@/hooks/store"; } from "@/hooks/store";
import { useIssues } from "@/hooks/store/use-issues"; import { useIssues } from "@/hooks/store/use-issues";
// components
// ui
// types
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
// constants
// helper
export const ProjectIssuesHeader: React.FC = observer(() => { export const ProjectIssuesHeader: React.FC = observer(() => {
// states // states
@ -109,6 +111,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
: currentProjectDetails?.total_issues : currentProjectDetails?.total_issues
: undefined; : undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -180,7 +184,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -2,21 +2,23 @@ import { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// icons
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// hooks
// components
// ui // ui
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui"; import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// helpers
// types
// constants
import { ProjectLogo } from "@/components/project"; import { ProjectLogo } from "@/components/project";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project"; import { EUserProjectRoles } from "@/constants/project";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper"; import { truncateText } from "@/helpers/string.helper";
// hooks
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -128,6 +130,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const canUserCreateIssue = const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="relative z-[15] flex h-[3.75rem] w-full items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4"> <div className="relative z-[15] flex h-[3.75rem] w-full items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -200,7 +204,12 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end" disabled={!canUserCreateIssue}> <FiltersDropdown
title="Filters"
placement="bottom-end"
disabled={!canUserCreateIssue}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,18 +1,21 @@
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// icons
import { Search, Plus, Briefcase, X, ListFilter } from "lucide-react"; import { Search, Plus, Briefcase, X, ListFilter } from "lucide-react";
// types
import { TProjectFilters } from "@plane/types"; import { TProjectFilters } from "@plane/types";
// hooks
// components
// ui // ui
import { Breadcrumbs, Button } from "@plane/ui"; import { Breadcrumbs, Button } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
// helpers
// constants
import { FiltersDropdown } from "@/components/issues"; import { FiltersDropdown } from "@/components/issues";
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project"; import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project";
// constants
import { EUserWorkspaceRoles } from "@/constants/workspace"; import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useApplication, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store"; import { useApplication, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -78,6 +81,8 @@ export const ProjectsHeader = observer(() => {
} }
}; };
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
return ( return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4"> <div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
@ -145,7 +150,12 @@ export const ProjectsHeader = observer(() => {
}); });
}} }}
/> />
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<ProjectFiltersSelection <ProjectFiltersSelection
displayFilters={displayFilters ?? {}} displayFilters={displayFilters ?? {}}
filters={filters ?? {}} filters={filters ?? {}}

View File

@ -8,6 +8,8 @@ import { ArchiveTabsList } from "@/components/archives";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants // constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
@ -62,6 +64,8 @@ export const ArchivedIssuesHeader: FC = observer(() => {
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property); updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property);
}; };
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="group relative flex border-b border-custom-border-200"> <div className="group relative flex border-b border-custom-border-200">
<div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm"> <div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm">
@ -69,7 +73,7 @@ export const ArchivedIssuesHeader: FC = observer(() => {
</div> </div>
{/* filter options */} {/* filter options */}
<div className="flex items-center gap-2 px-8"> <div className="flex items-center gap-2 px-8">
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters || {}} filters={issueFilters?.filters || {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,11 +1,12 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import { Placement } from "@popperjs/core"; import { Placement } from "@popperjs/core";
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
// icons
import { ChevronUp } from "lucide-react"; import { ChevronUp } from "lucide-react";
// headless ui
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
// ui // ui
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
// icons
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
@ -15,10 +16,20 @@ type Props = {
disabled?: boolean; disabled?: boolean;
tabIndex?: number; tabIndex?: number;
menuButton?: React.ReactNode; menuButton?: React.ReactNode;
isFiltersApplied?: boolean;
}; };
export const FiltersDropdown: React.FC<Props> = (props) => { export const FiltersDropdown: React.FC<Props> = (props) => {
const { children, icon, title = "Dropdown", placement, disabled = false, tabIndex, menuButton } = props; const {
children,
icon,
title = "Dropdown",
placement,
disabled = false,
tabIndex,
menuButton,
isFiltersApplied = false,
} = props;
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null); const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
@ -50,10 +61,16 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
<ChevronUp className={`transition-all ${open ? "" : "rotate-180"}`} size={14} strokeWidth={2} /> <ChevronUp className={`transition-all ${open ? "" : "rotate-180"}`} size={14} strokeWidth={2} />
} }
tabIndex={tabIndex} tabIndex={tabIndex}
className="relative"
> >
<div className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}> <>
<span>{title}</span> <div className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}>
</div> <span>{title}</span>
</div>
{isFiltersApplied && (
<span className="absolute h-2 w-2 -right-0.5 -top-0.5 bg-custom-primary-100 rounded-full" />
)}
</>
</Button> </Button>
)} )}
</Popover.Button> </Popover.Button>
@ -73,7 +90,9 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
style={styles.popper} style={styles.popper}
{...attributes.popper} {...attributes.popper}
> >
<div className="flex max-h-[30rem] lg:max-h-[37.5rem] w-[18.75rem] flex-col overflow-hidden">{children}</div> <div className="flex max-h-[30rem] lg:max-h-[37.5rem] w-[18.75rem] flex-col overflow-hidden">
{children}
</div>
</div> </div>
</Popover.Panel> </Popover.Panel>
</Transition> </Transition>

View File

@ -1,18 +1,21 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import router from "next/router"; import router from "next/router";
// components
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
import { CustomMenu } from "@plane/ui";
// icons // icons
// constants import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { CustomMenu } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
// layouts
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "./issue-layouts";
export const IssuesMobileHeader = observer(() => { export const IssuesMobileHeader = observer(() => {
const layouts = [ const layouts = [
@ -83,6 +86,8 @@ export const IssuesMobileHeader = observer(() => {
[workspaceSlug, projectId, updateFilters] [workspaceSlug, projectId, updateFilters]
); );
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<> <>
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -122,6 +127,7 @@ export const IssuesMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" /> <ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span> </span>
} }
isFiltersApplied={isFiltersApplied}
> >
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}

View File

@ -11,6 +11,7 @@ import { FiltersDropdown } from "@/components/issues";
import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules"; import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useMember, useModuleFilter } from "@/hooks/store"; import { useMember, useModuleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -70,6 +71,8 @@ export const ArchivedModulesHeader: FC = observer(() => {
} }
}; };
const isFiltersApplied = calculateTotalFilters(currentProjectArchivedFilters ?? {}) !== 0;
return ( return (
<div className="group relative flex border-b border-custom-border-200"> <div className="group relative flex border-b border-custom-border-200">
<div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm"> <div className="flex w-full items-center overflow-x-auto px-4 gap-2 horizontal-scrollbar scrollbar-sm">
@ -128,7 +131,12 @@ export const ArchivedModulesHeader: FC = observer(() => {
}); });
}} }}
/> />
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<ModuleFiltersSelection <ModuleFiltersSelection
displayFilters={currentProjectDisplayFilters ?? {}} displayFilters={currentProjectDisplayFilters ?? {}}
filters={currentProjectArchivedFilters ?? {}} filters={currentProjectArchivedFilters ?? {}}

View File

@ -3,17 +3,19 @@ import { observer } from "mobx-react";
import router from "next/router"; import router from "next/router";
// icons // icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui // ui
import { CustomMenu } from "@plane/ui"; import { CustomMenu } from "@plane/ui";
// components // components
import { ProjectAnalyticsModal } from "@/components/analytics"; import { ProjectAnalyticsModal } from "@/components/analytics";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// hooks
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
import { useIssues, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
// types
// constants // constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
export const ModuleMobileHeader = observer(() => { export const ModuleMobileHeader = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
@ -86,6 +88,8 @@ export const ModuleMobileHeader = observer(() => {
[workspaceSlug, projectId, moduleId, updateFilters] [workspaceSlug, projectId, moduleId, updateFilters]
); );
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="block md:hidden"> <div className="block md:hidden">
<ProjectAnalyticsModal <ProjectAnalyticsModal
@ -125,6 +129,7 @@ export const ModuleMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" /> <ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span> </span>
} }
isFiltersApplied={isFiltersApplied}
> >
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}

View File

@ -1,7 +1,9 @@
import React, { FC, useCallback, useRef, useState } from "react"; import React, { FC, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// icons
import { ListFilter, Search, X } from "lucide-react"; import { ListFilter, Search, X } from "lucide-react";
// helpers
import { cn } from "@plane/editor-core"; import { cn } from "@plane/editor-core";
// types // types
import { TModuleFilters } from "@plane/types"; import { TModuleFilters } from "@plane/types";
@ -12,6 +14,8 @@ import { FiltersDropdown } from "@/components/issues";
import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules/dropdowns"; import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules/dropdowns";
// constants // constants
import { MODULE_VIEW_LAYOUTS } from "@/constants/module"; import { MODULE_VIEW_LAYOUTS } from "@/constants/module";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useMember, useModuleFilter } from "@/hooks/store"; import { useMember, useModuleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -79,6 +83,9 @@ export const ModuleViewHeader: FC = observer(() => {
useOutsideClickDetector(inputRef, () => { useOutsideClickDetector(inputRef, () => {
if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false);
}); });
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
return ( return (
<div className="hidden h-full sm:flex items-center gap-3 self-end"> <div className="hidden h-full sm:flex items-center gap-3 self-end">
<div className="flex items-center"> <div className="flex items-center">
@ -135,7 +142,12 @@ export const ModuleViewHeader: FC = observer(() => {
}); });
}} }}
/> />
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<ModuleFiltersSelection <ModuleFiltersSelection
displayFilters={displayFilters ?? {}} displayFilters={displayFilters ?? {}}
filters={filters ?? {}} filters={filters ?? {}}

View File

@ -45,6 +45,8 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
[filters.filters, updateFilters] [filters.filters, updateFilters]
); );
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
return ( return (
<> <>
<div className="flex-shrink-0 h-[50px] w-full border-b border-custom-border-200 px-6 relative flex items-center gap-4 justify-between"> <div className="flex-shrink-0 h-[50px] w-full border-b border-custom-border-200 px-6 relative flex items-center gap-4 justify-between">
@ -59,7 +61,12 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
if (val.order) updateFilters("sortBy", val.order); if (val.order) updateFilters("sortBy", val.order);
}} }}
/> />
<FiltersDropdown icon={<ListFilter className="h-3 w-3" />} title="Filters" placement="bottom-end"> <FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<PageFiltersSelection <PageFiltersSelection
filters={filters} filters={filters}
handleFiltersUpdate={updateFilters} handleFiltersUpdate={updateFilters}

View File

@ -1,13 +1,16 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// components // components
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "@/components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "@/components/issues";
// hooks
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { useIssues, useLabel } from "@/hooks/store";
// constants // constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useLabel } from "@/hooks/store";
export const ProfileIssuesFilter = observer(() => { export const ProfileIssuesFilter = observer(() => {
// router // router
@ -93,6 +96,8 @@ export const ProfileIssuesFilter = observer(() => {
[workspaceSlug, updateFilters, userId] [workspaceSlug, updateFilters, userId]
); );
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="relative flex items-center justify-end gap-2"> <div className="relative flex items-center justify-end gap-2">
<LayoutSelection <LayoutSelection
@ -101,7 +106,7 @@ export const ProfileIssuesFilter = observer(() => {
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end"> <FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection <FilterSelection
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined

View File

@ -11,10 +11,11 @@ import { CustomMenu } from "@plane/ui";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants // constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useIssues, useLabel } from "@/hooks/store"; import { useIssues, useLabel } from "@/hooks/store";
const ProfileIssuesMobileHeader = observer(() => { const ProfileIssuesMobileHeader = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
@ -99,6 +100,9 @@ const ProfileIssuesMobileHeader = observer(() => {
}, },
[workspaceSlug, updateFilters, userId] [workspaceSlug, updateFilters, userId]
); );
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return ( return (
<div className="flex justify-evenly border-b border-custom-border-200 py-2 md:hidden"> <div className="flex justify-evenly border-b border-custom-border-200 py-2 md:hidden">
<CustomMenu <CustomMenu
@ -135,6 +139,7 @@ const ProfileIssuesMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" /> <ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span> </span>
} }
isFiltersApplied={isFiltersApplied}
> >
<FilterSelection <FilterSelection
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={

View File

@ -5,10 +5,12 @@ import { ChevronDown, ListFilter } from "lucide-react";
// types // types
import { TProjectFilters } from "@plane/types"; import { TProjectFilters } from "@plane/types";
// hooks // hooks
import { FiltersDropdown } from "@/components/issues/issue-layouts";
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project/dropdowns";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useApplication, useMember, useProjectFilter } from "@/hooks/store"; import { useApplication, useMember, useProjectFilter } from "@/hooks/store";
// components
import { FiltersDropdown } from "../issues";
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "./dropdowns";
const ProjectsMobileHeader = observer(() => { const ProjectsMobileHeader = observer(() => {
const { const {
@ -44,6 +46,8 @@ const ProjectsMobileHeader = observer(() => {
[filters, updateFilters, workspaceSlug] [filters, updateFilters, workspaceSlug]
); );
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
return ( return (
<div className="flex py-2 border-b border-custom-border-200 md:hidden bg-custom-background-100 w-full"> <div className="flex py-2 border-b border-custom-border-200 md:hidden bg-custom-background-100 w-full">
<ProjectOrderByDropdown <ProjectOrderByDropdown
@ -68,6 +72,7 @@ const ProjectsMobileHeader = observer(() => {
<ChevronDown className="h-3 w-3" strokeWidth={2} /> <ChevronDown className="h-3 w-3" strokeWidth={2} />
</div> </div>
} }
isFiltersApplied={isFiltersApplied}
> >
<ProjectFiltersSelection <ProjectFiltersSelection
displayFilters={displayFilters ?? {}} displayFilters={displayFilters ?? {}}

View File

@ -34,7 +34,7 @@ export const ViewListItemAction: FC<Props> = observer((props) => {
// derived values // derived values
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
// @ts-expect-error key types are not compatible
const totalFilters = calculateTotalFilters(view.filters ?? {}); const totalFilters = calculateTotalFilters(view.filters ?? {});
// handlers // handlers

View File

@ -1,23 +1,18 @@
import differenceInCalendarDays from "date-fns/differenceInCalendarDays"; import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
// helpers // helpers
import { getDate } from "./date-time.helper"; import { getDate } from "./date-time.helper";
// types
// import { IIssueFilterOptions } from "@plane/types"; // import { IIssueFilterOptions } from "@plane/types";
type TFilters = {
[key: string]: boolean | string[] | null;
};
/** /**
* @description calculates the total number of filters applied * @description calculates the total number of filters applied
* @param {TFilters} filters * @param {T} filters
* @returns {number} * @returns {number}
*/ */
export const calculateTotalFilters = (filters: TFilters): number => export const calculateTotalFilters = <T>(filters: T): number =>
filters && Object.keys(filters).length > 0 filters && Object.keys(filters).length > 0
? Object.keys(filters) ? Object.keys(filters)
.map((key) => { .map((key) => {
const value = filters[key as keyof TFilters]; const value = filters[key as keyof T];
if (value === null) return 0; if (value === null) return 0;
if (Array.isArray(value)) return value.length; if (Array.isArray(value)) return value.length;
if (typeof value === "boolean") return value ? 1 : 0; if (typeof value === "boolean") return value ? 1 : 0;
@ -25,7 +20,6 @@ export const calculateTotalFilters = (filters: TFilters): number =>
}) })
.reduce((curr, prev) => curr + prev, 0) .reduce((curr, prev) => curr + prev, 0)
: 0; : 0;
/** /**
* @description checks if the date satisfies the filter * @description checks if the date satisfies the filter
* @param {Date} date * @param {Date} date