[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";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useCycleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -61,6 +62,8 @@ export const ArchivedCyclesHeader: FC = observer(() => {
}
};
const isFiltersApplied = calculateTotalFilters(currentProjectArchivedFilters ?? {}) !== 0;
return (
<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">
@ -110,7 +113,12 @@ export const ArchivedCyclesHeader: FC = observer(() => {
</button>
)}
</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={currentProjectArchivedFilters ?? {}}
handleFiltersUpdate={handleFilters}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,19 @@
import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// 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
// 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";
// constants
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 { usePlatformOS } from "@/hooks/use-platform-os";
@ -83,6 +85,8 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
: currentProjectDetails.draft_issues
: undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
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="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)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end">
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}

View File

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

View File

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

View File

@ -1,18 +1,21 @@
import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
// icons
import { Search, Plus, Briefcase, X, ListFilter } from "lucide-react";
// types
import { TProjectFilters } from "@plane/types";
// hooks
// components
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
// helpers
// constants
import { FiltersDropdown } from "@/components/issues";
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project";
// constants
import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useApplication, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -78,6 +81,8 @@ export const ProjectsHeader = observer(() => {
}
};
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
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="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
displayFilters={displayFilters ?? {}}
filters={filters ?? {}}

View File

@ -8,6 +8,8 @@ import { ArchiveTabsList } from "@/components/archives";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants
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";
@ -62,6 +64,8 @@ export const ArchivedIssuesHeader: FC = observer(() => {
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property);
};
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<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">
@ -69,7 +73,7 @@ export const ArchivedIssuesHeader: FC = observer(() => {
</div>
{/* filter options */}
<div className="flex items-center gap-2 px-8">
<FiltersDropdown title="Filters" placement="bottom-end">
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters || {}}
handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,11 +1,12 @@
import React, { Fragment, useState } from "react";
import { Placement } from "@popperjs/core";
import { usePopper } from "react-popper";
// icons
import { ChevronUp } from "lucide-react";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// ui
import { Button } from "@plane/ui";
// icons
type Props = {
children: React.ReactNode;
@ -15,10 +16,20 @@ type Props = {
disabled?: boolean;
tabIndex?: number;
menuButton?: React.ReactNode;
isFiltersApplied?: boolean;
};
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 [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} />
}
tabIndex={tabIndex}
className="relative"
>
<div className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}>
<span>{title}</span>
</div>
<>
<div className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}>
<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>
)}
</Popover.Button>
@ -73,7 +90,9 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
style={styles.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>
</Popover.Panel>
</Transition>

View File

@ -1,18 +1,21 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react";
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
// 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 { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
// 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, useProject, useProjectState } from "@/hooks/store";
// layouts
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "./issue-layouts";
export const IssuesMobileHeader = observer(() => {
const layouts = [
@ -83,6 +86,8 @@ export const IssuesMobileHeader = observer(() => {
[workspaceSlug, projectId, updateFilters]
);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<ProjectAnalyticsModal
@ -122,6 +127,7 @@ export const IssuesMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span>
}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}

View File

@ -11,6 +11,7 @@ import { FiltersDropdown } from "@/components/issues";
import { ModuleFiltersSelection, ModuleOrderByDropdown } from "@/components/modules";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useMember, useModuleFilter } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
@ -70,6 +71,8 @@ export const ArchivedModulesHeader: FC = observer(() => {
}
};
const isFiltersApplied = calculateTotalFilters(currentProjectArchivedFilters ?? {}) !== 0;
return (
<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">
@ -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
displayFilters={currentProjectDisplayFilters ?? {}}
filters={currentProjectArchivedFilters ?? {}}

View File

@ -3,17 +3,19 @@ import { observer } from "mobx-react";
import router from "next/router";
// icons
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 { 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
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(() => {
const [analyticsModal, setAnalyticsModal] = useState(false);
@ -86,6 +88,8 @@ export const ModuleMobileHeader = observer(() => {
[workspaceSlug, projectId, moduleId, updateFilters]
);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<div className="block md:hidden">
<ProjectAnalyticsModal
@ -125,6 +129,7 @@ export const ModuleMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span>
}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}

View File

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

View File

@ -45,6 +45,8 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
[filters.filters, updateFilters]
);
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
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">
@ -59,7 +61,12 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
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
filters={filters}
handleFiltersUpdate={updateFilters}

View File

@ -1,13 +1,16 @@
import { useCallback } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// components
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
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(() => {
// router
@ -93,6 +96,8 @@ export const ProfileIssuesFilter = observer(() => {
[workspaceSlug, updateFilters, userId]
);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<div className="relative flex items-center justify-end gap-2">
<LayoutSelection
@ -101,7 +106,7 @@ export const ProfileIssuesFilter = observer(() => {
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end">
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
layoutDisplayFiltersOptions={
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";
// 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 } from "@/hooks/store";
const ProfileIssuesMobileHeader = observer(() => {
// router
const router = useRouter();
@ -99,6 +100,9 @@ const ProfileIssuesMobileHeader = observer(() => {
},
[workspaceSlug, updateFilters, userId]
);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<div className="flex justify-evenly border-b border-custom-border-200 py-2 md:hidden">
<CustomMenu
@ -135,6 +139,7 @@ const ProfileIssuesMobileHeader = observer(() => {
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
</span>
}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection
layoutDisplayFiltersOptions={

View File

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

View File

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

View File

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