mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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:
parent
1eba6c24cd
commit
2aef40b7c5
@ -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}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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={
|
||||
|
@ -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 ?? {}}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user