diff --git a/web/components/cycles/archived-cycles/header.tsx b/web/components/cycles/archived-cycles/header.tsx index f5edc3bd3..e45b50baa 100644 --- a/web/components/cycles/archived-cycles/header.tsx +++ b/web/components/cycles/archived-cycles/header.tsx @@ -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 (
@@ -110,7 +113,12 @@ export const ArchivedCyclesHeader: FC = observer(() => { )}
- } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + > { @@ -103,6 +107,8 @@ export const CycleMobileHeader = () => { [workspaceSlug, projectId, cycleId, updateFilters] ); + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return ( <> { } + isFiltersApplied={isFiltersApplied} > = observer((props) => { } }; + const isFiltersApplied = calculateTotalFilters(currentProjectFilters ?? {}) !== 0; + return (
@@ -135,7 +140,12 @@ export const CyclesViewHeader: React.FC = observer((props) => { )}
- } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + >
diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index c347fb62f..73e7f5983 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -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 ( <> { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return ( <> setCreateViewModal(false)} /> @@ -110,7 +114,7 @@ export const GlobalIssuesHeader: React.FC = observer(() => {
<> - + = ({ moduleId }) => { // router @@ -152,6 +153,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => { : moduleDetails.total_issues : undefined; + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return ( <> { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { : currentProjectDetails.draft_issues : undefined; + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
@@ -131,7 +135,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { // states @@ -109,6 +111,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => { : currentProjectDetails?.total_issues : undefined; + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return ( <> { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { const canUserCreateIssue = currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
@@ -200,7 +204,12 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { selectedLayout={activeLayout} /> - + { } }; + const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; + return (
@@ -145,7 +150,12 @@ export const ProjectsHeader = observer(() => { }); }} /> - } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + > { updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property); }; + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
@@ -69,7 +73,7 @@ export const ArchivedIssuesHeader: FC = observer(() => {
{/* filter options */}
- + = (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(null); const [popperElement, setPopperElement] = useState(null); @@ -50,10 +61,16 @@ export const FiltersDropdown: React.FC = (props) => { } tabIndex={tabIndex} + className="relative" > -
- {title} -
+ <> +
+ {title} +
+ {isFiltersApplied && ( + + )} + )} @@ -73,7 +90,9 @@ export const FiltersDropdown: React.FC = (props) => { style={styles.popper} {...attributes.popper} > -
{children}
+
+ {children} +
diff --git a/web/components/issues/issues-mobile-header.tsx b/web/components/issues/issues-mobile-header.tsx index de1c088e0..72df560a9 100644 --- a/web/components/issues/issues-mobile-header.tsx +++ b/web/components/issues/issues-mobile-header.tsx @@ -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 ( <> { } + isFiltersApplied={isFiltersApplied} > { } }; + const isFiltersApplied = calculateTotalFilters(currentProjectArchivedFilters ?? {}) !== 0; + return (
@@ -128,7 +131,12 @@ export const ArchivedModulesHeader: FC = observer(() => { }); }} /> - } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + > { const [analyticsModal, setAnalyticsModal] = useState(false); @@ -86,6 +88,8 @@ export const ModuleMobileHeader = observer(() => { [workspaceSlug, projectId, moduleId, updateFilters] ); + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
{ } + isFiltersApplied={isFiltersApplied} > { useOutsideClickDetector(inputRef, () => { if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); }); + + const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; + return (
@@ -135,7 +142,12 @@ export const ModuleViewHeader: FC = observer(() => { }); }} /> - } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + > = observer((props) => { [filters.filters, updateFilters] ); + const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; + return ( <>
@@ -59,7 +61,12 @@ export const PagesListHeaderRoot: React.FC = observer((props) => { if (val.order) updateFilters("sortBy", val.order); }} /> - } title="Filters" placement="bottom-end"> + } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + > { // router @@ -93,6 +96,8 @@ export const ProfileIssuesFilter = observer(() => { [workspaceSlug, updateFilters, userId] ); + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
{ selectedLayout={activeLayout} /> - + { // router const router = useRouter(); @@ -99,6 +100,9 @@ const ProfileIssuesMobileHeader = observer(() => { }, [workspaceSlug, updateFilters, userId] ); + + const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0; + return (
{ } + isFiltersApplied={isFiltersApplied} > { const { @@ -44,6 +46,8 @@ const ProjectsMobileHeader = observer(() => { [filters, updateFilters, workspaceSlug] ); + const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; + return (
{
} + isFiltersApplied={isFiltersApplied} > = observer((props) => { // derived values const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; - // @ts-expect-error key types are not compatible + const totalFilters = calculateTotalFilters(view.filters ?? {}); // handlers diff --git a/web/helpers/filter.helper.ts b/web/helpers/filter.helper.ts index c204f00be..670d6d0e0 100644 --- a/web/helpers/filter.helper.ts +++ b/web/helpers/filter.helper.ts @@ -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 = (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