[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:
guru_sainath 2024-04-15 12:48:43 +05:30 committed by GitHub
parent 3e2355e223
commit a44a032683
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 155 additions and 1 deletions

View File

@ -29,6 +29,7 @@ export type TInboxIssueFilter = {
} & {
[key in TInboxIssueFilterDateKeys]: string[] | undefined;
} & {
state: string[] | undefined;
status: TInboxIssueStatus[] | undefined;
priority: TIssuePriorities[] | undefined;
labels: string[] | undefined;

View File

@ -1,5 +1,6 @@
export * from "./root";
export * from "./status";
export * from "./state";
export * from "./priority";
export * from "./member";
export * from "./label";

View File

@ -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 */}

View 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>
);
});

View File

@ -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} />

View File

@ -1,5 +1,6 @@
export * from "./filter-selection";
export * from "./status";
export * from "./state";
export * from "./priority";
export * from "./labels";
export * from "./members";

View 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>
)}
</>
);
});

View File

@ -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");
};