forked from github/plane
[WEB-984] feat: integrated state filter in inbox issues filter (#4182)
* chore: added state filtering in the inbox filters * chore: Clearing the issues when we apply filters
This commit is contained in:
parent
3e2355e223
commit
a44a032683
1
packages/types/src/inbox.d.ts
vendored
1
packages/types/src/inbox.d.ts
vendored
@ -29,6 +29,7 @@ export type TInboxIssueFilter = {
|
||||
} & {
|
||||
[key in TInboxIssueFilterDateKeys]: string[] | undefined;
|
||||
} & {
|
||||
state: string[] | undefined;
|
||||
status: TInboxIssueStatus[] | undefined;
|
||||
priority: TIssuePriorities[] | undefined;
|
||||
labels: string[] | undefined;
|
||||
|
@ -1,5 +1,6 @@
|
||||
export * from "./root";
|
||||
export * from "./status";
|
||||
export * from "./state";
|
||||
export * from "./priority";
|
||||
export * from "./member";
|
||||
export * from "./label";
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
InboxIssueAppliedFiltersMember,
|
||||
InboxIssueAppliedFiltersLabel,
|
||||
InboxIssueAppliedFiltersDate,
|
||||
InboxIssueAppliedFiltersState,
|
||||
} from "@/components/inbox";
|
||||
// hooks
|
||||
import { useProjectInbox } from "@/hooks/store";
|
||||
@ -19,6 +20,8 @@ export const InboxIssueAppliedFilters: FC = observer(() => {
|
||||
<div className="p-3 py-2 relative flex flex-wrap items-center gap-1 border-b border-custom-border-300">
|
||||
{/* status */}
|
||||
<InboxIssueAppliedFiltersStatus />
|
||||
{/* state */}
|
||||
<InboxIssueAppliedFiltersState />
|
||||
{/* priority */}
|
||||
<InboxIssueAppliedFiltersPriority />
|
||||
{/* assignees */}
|
||||
|
52
web/components/inbox/inbox-filter/applied-filters/state.tsx
Normal file
52
web/components/inbox/inbox-filter/applied-filters/state.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// hooks
|
||||
import { useProjectInbox, useProjectState } from "@/hooks/store";
|
||||
|
||||
export const InboxIssueAppliedFiltersState: FC = observer(() => {
|
||||
// hooks
|
||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||
const { getStateById } = useProjectState();
|
||||
// derived values
|
||||
const filteredValues = inboxFilters?.state || [];
|
||||
const currentOptionDetail = (stateId: string) => getStateById(stateId) || undefined;
|
||||
|
||||
const handleFilterValue = (value: string): string[] =>
|
||||
filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value];
|
||||
|
||||
const clearFilter = () => handleInboxIssueFilters("state", undefined);
|
||||
|
||||
if (filteredValues.length === 0) return <></>;
|
||||
return (
|
||||
<div className="relative flex flex-wrap items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1">
|
||||
<div className="text-xs text-custom-text-200">Status</div>
|
||||
{filteredValues.map((value) => {
|
||||
const optionDetail = currentOptionDetail(value);
|
||||
if (!optionDetail) return <></>;
|
||||
return (
|
||||
<div key={value} className="relative flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<div className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden">
|
||||
<StateGroupIcon color={optionDetail.color} stateGroup={optionDetail.group} height="12px" width="12px" />
|
||||
</div>
|
||||
<div className="text-xs truncate">{optionDetail?.name}</div>
|
||||
<div
|
||||
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
||||
onClick={() => handleInboxIssueFilters("state", handleFilterValue(optionDetail?.id))}
|
||||
>
|
||||
<X className={`w-3 h-3`} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
||||
onClick={clearFilter}
|
||||
>
|
||||
<X className={`w-3 h-3`} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -8,9 +8,10 @@ import {
|
||||
FilterMember,
|
||||
FilterDate,
|
||||
FilterLabels,
|
||||
FilterState,
|
||||
} from "@/components/inbox/inbox-filter/filters";
|
||||
// hooks
|
||||
import { useMember, useLabel } from "@/hooks/store";
|
||||
import { useMember, useLabel, useProjectState } from "@/hooks/store";
|
||||
|
||||
export const InboxIssueFilterSelection: FC = observer(() => {
|
||||
// hooks
|
||||
@ -18,6 +19,7 @@ export const InboxIssueFilterSelection: FC = observer(() => {
|
||||
project: { projectMemberIds },
|
||||
} = useMember();
|
||||
const { projectLabels } = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
|
||||
@ -47,6 +49,10 @@ export const InboxIssueFilterSelection: FC = observer(() => {
|
||||
<div className="py-2">
|
||||
<FilterStatus searchQuery={filtersSearchQuery} />
|
||||
</div>
|
||||
{/* state */}
|
||||
<div className="py-2">
|
||||
<FilterState states={projectStates} searchQuery={filtersSearchQuery} />
|
||||
</div>
|
||||
{/* Priority */}
|
||||
<div className="py-2">
|
||||
<FilterPriority searchQuery={filtersSearchQuery} />
|
||||
|
@ -1,5 +1,6 @@
|
||||
export * from "./filter-selection";
|
||||
export * from "./status";
|
||||
export * from "./state";
|
||||
export * from "./priority";
|
||||
export * from "./labels";
|
||||
export * from "./members";
|
||||
|
84
web/components/inbox/inbox-filter/filters/state.tsx
Normal file
84
web/components/inbox/inbox-filter/filters/state.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { IState } from "@plane/types";
|
||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// hooks
|
||||
import { useProjectInbox } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
states: IState[] | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterState: FC<Props> = observer((props) => {
|
||||
const { states, searchQuery } = props;
|
||||
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||
|
||||
const filterValue = inboxFilters?.state || [];
|
||||
|
||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||
|
||||
const filteredOptions = states?.filter((state) => state.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!filteredOptions) return;
|
||||
|
||||
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
|
||||
else setItemsToRender(filteredOptions.length);
|
||||
};
|
||||
|
||||
const handleFilterValue = (value: string): string[] =>
|
||||
filterValue?.includes(value) ? filterValue.filter((v) => v !== value) : [...filterValue, value];
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterHeader
|
||||
title={`State${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
|
||||
isPreviewEnabled={previewEnabled}
|
||||
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
|
||||
/>
|
||||
{previewEnabled && (
|
||||
<div>
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((state) => (
|
||||
<FilterOption
|
||||
key={state?.id}
|
||||
isChecked={filterValue?.includes(state?.id) ? true : false}
|
||||
onClick={() => handleInboxIssueFilters("state", handleFilterValue(state.id))}
|
||||
icon={<StateGroupIcon color={state.color} stateGroup={state.group} height="12px" width="12px" />}
|
||||
title={state.name}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
className="ml-8 text-xs font-medium text-custom-primary-100"
|
||||
onClick={handleViewToggle}
|
||||
>
|
||||
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
||||
)
|
||||
) : (
|
||||
<Loader className="space-y-2">
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
@ -216,6 +216,8 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
||||
set(this, "inboxFilters", undefined);
|
||||
set(this, ["inboxSorting", "order_by"], "issue__created_at");
|
||||
set(this, ["inboxSorting", "sort_by"], "desc");
|
||||
set(this, ["inboxIssues"], {});
|
||||
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||
if (tab === "closed") set(this, ["inboxFilters", "status"], [-1, 1, 2]);
|
||||
else set(this, ["inboxFilters", "status"], [-2]);
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
@ -224,12 +226,16 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
||||
|
||||
handleInboxIssueFilters = <T extends keyof TInboxIssueFilter>(key: T, value: TInboxIssueFilter[T]) => {
|
||||
set(this.inboxFilters, key, value);
|
||||
set(this, ["inboxIssues"], {});
|
||||
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
||||
};
|
||||
|
||||
handleInboxIssueSorting = <T extends keyof TInboxIssueSorting>(key: T, value: TInboxIssueSorting[T]) => {
|
||||
set(this.inboxSorting, key, value);
|
||||
set(this, ["inboxIssues"], {});
|
||||
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user