forked from github/plane
chore: show inbox applied filters list (#1334)
This commit is contained in:
parent
7d29a89eed
commit
a0ae569a68
@ -6,7 +6,7 @@ import { getPriorityIcon } from "components/icons";
|
||||
import { IInboxFilterOptions } from "types";
|
||||
// constants
|
||||
import { PRIORITIES } from "constants/project";
|
||||
import { STATUS } from "constants/inbox";
|
||||
import { INBOX_STATUS } from "constants/inbox";
|
||||
|
||||
type Props = {
|
||||
filters: Partial<IInboxFilterOptions>;
|
||||
@ -45,16 +45,16 @@ export const FiltersDropdown: React.FC<Props> = ({ filters, onSelect, direction,
|
||||
{
|
||||
id: "inbox_status",
|
||||
label: "Status",
|
||||
value: Object.values(STATUS),
|
||||
value: INBOX_STATUS.map((status) => status.value),
|
||||
children: [
|
||||
...Object.keys(STATUS).map((status) => ({
|
||||
id: status,
|
||||
label: status,
|
||||
...INBOX_STATUS.map((status) => ({
|
||||
id: status.key,
|
||||
label: status.label,
|
||||
value: {
|
||||
key: "inbox_status",
|
||||
value: STATUS[status],
|
||||
value: status.value,
|
||||
},
|
||||
selected: filters?.inbox_status?.includes(STATUS[status]),
|
||||
selected: filters?.inbox_status?.includes(status.value),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
126
apps/app/components/inbox/filters-list.tsx
Normal file
126
apps/app/components/inbox/filters-list.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
// hooks
|
||||
import useInboxView from "hooks/use-inbox-view";
|
||||
// icons
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { getPriorityIcon } from "components/icons";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
// constants
|
||||
import { INBOX_STATUS } from "constants/inbox";
|
||||
|
||||
export const InboxFiltersList = () => {
|
||||
const { filters, setFilters, clearAllFilters, filtersLength } = useInboxView();
|
||||
|
||||
if (filtersLength <= 0) return <></>;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 flex-wrap text-[0.65rem] p-3">
|
||||
{Object.keys(filters).map((key) => {
|
||||
const filterKey = key as keyof typeof filters;
|
||||
|
||||
if (filters[filterKey] !== null)
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="flex items-center gap-x-2 rounded-full border border-brand-base bg-brand-surface-2 px-2 py-1"
|
||||
>
|
||||
<span className="capitalize text-brand-secondary">
|
||||
{replaceUnderscoreIfSnakeCase(key)}:
|
||||
</span>
|
||||
{filters[filterKey] === null || (filters[filterKey]?.length ?? 0) <= 0 ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 font-medium">None</span>
|
||||
) : (
|
||||
<div className="space-x-2">
|
||||
{filterKey === "priority" ? (
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{filters.priority?.map((priority) => (
|
||||
<div
|
||||
key={priority}
|
||||
className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${
|
||||
priority === "urgent"
|
||||
? "bg-red-500/20 text-red-500"
|
||||
: priority === "high"
|
||||
? "bg-orange-500/20 text-orange-500"
|
||||
: priority === "medium"
|
||||
? "bg-yellow-500/20 text-yellow-500"
|
||||
: priority === "low"
|
||||
? "bg-green-500/20 text-green-500"
|
||||
: "bg-brand-surface-1 text-brand-secondary"
|
||||
}`}
|
||||
>
|
||||
<span>{getPriorityIcon(priority)}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer"
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
priority: filters.priority?.filter((p) => p !== priority),
|
||||
})
|
||||
}
|
||||
>
|
||||
<XMarkIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
priority: null,
|
||||
})
|
||||
}
|
||||
>
|
||||
<XMarkIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
) : filterKey === "inbox_status" ? (
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{filters.inbox_status?.map((status) => (
|
||||
<div
|
||||
key={status}
|
||||
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize bg-brand-surface-1 text-brand-secondary"
|
||||
>
|
||||
<span>{INBOX_STATUS.find((s) => s.value === status)?.label}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer"
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
inbox_status: filters.inbox_status?.filter((p) => p !== status),
|
||||
})
|
||||
}
|
||||
>
|
||||
<XMarkIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
priority: null,
|
||||
})
|
||||
}
|
||||
>
|
||||
<XMarkIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
(filters[filterKey] as any)?.join(", ")
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearAllFilters}
|
||||
className="flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-surface-2 px-3 py-1.5 text-brand-secondary hover:text-brand-base"
|
||||
>
|
||||
<span>Clear all</span>
|
||||
<XMarkIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
export * from "./decline-issue-modal";
|
||||
export * from "./delete-issue-modal";
|
||||
export * from "./filters-dropdown";
|
||||
export * from "./filters-list";
|
||||
export * from "./inbox-action-headers";
|
||||
export * from "./inbox-issue-card";
|
||||
export * from "./inbox-main-content";
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import useInboxView from "hooks/use-inbox-view";
|
||||
// components
|
||||
import { InboxIssueCard } from "components/inbox";
|
||||
import { InboxIssueCard, InboxFiltersList } from "components/inbox";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
|
||||
@ -14,10 +14,11 @@ export const IssuesListSidebar = () => {
|
||||
const { issues: inboxIssues } = useInboxView();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full flex flex-col overflow-hidden">
|
||||
<InboxFiltersList />
|
||||
{inboxIssues ? (
|
||||
inboxIssues.length > 0 ? (
|
||||
<div className="divide-y divide-brand-base overflow-auto h-full pb-10">
|
||||
<div className="divide-y divide-brand-base overflow-auto h-full">
|
||||
{inboxIssues.map((issue) => (
|
||||
<InboxIssueCard
|
||||
key={issue.id}
|
||||
@ -39,6 +40,6 @@ export const IssuesListSidebar = () => {
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,29 @@
|
||||
export const STATUS: { [key: string]: number } = {
|
||||
Pending: -2,
|
||||
Declined: -1,
|
||||
Snoozed: 0,
|
||||
Accepted: 1,
|
||||
Duplicate: 2,
|
||||
};
|
||||
export const INBOX_STATUS = [
|
||||
{
|
||||
key: "pending",
|
||||
label: "Pending",
|
||||
value: -2,
|
||||
},
|
||||
{
|
||||
key: "declined",
|
||||
label: "Declined",
|
||||
value: -1,
|
||||
},
|
||||
{
|
||||
key: "snoozed",
|
||||
label: "Snoozed",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
key: "accepted",
|
||||
label: "Accepted",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
label: "Duplicate",
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
export const INBOX_ISSUE_SOURCE = "in-app";
|
||||
|
@ -26,6 +26,7 @@ type ReducerActionType = {
|
||||
|
||||
type ContextType = InboxViewProps & {
|
||||
setFilters: (filters: Partial<IInboxFilterOptions>) => void;
|
||||
clearAllFilters: () => void;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
@ -53,7 +54,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
||||
...state,
|
||||
filters: {
|
||||
...state.filters,
|
||||
...payload,
|
||||
...payload?.filters,
|
||||
},
|
||||
};
|
||||
|
||||
@ -140,6 +141,38 @@ export const InboxViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
||||
[workspaceSlug, projectId, inboxId, mutateInboxDetails, state]
|
||||
);
|
||||
|
||||
const clearAllFilters = useCallback(() => {
|
||||
dispatch({
|
||||
type: "SET_FILTERS",
|
||||
payload: {
|
||||
filters: { ...initialState.filters },
|
||||
},
|
||||
});
|
||||
|
||||
if (!workspaceSlug || !projectId || !inboxId) return;
|
||||
|
||||
const newViewProps = {
|
||||
...state,
|
||||
filters: { ...initialState.filters },
|
||||
};
|
||||
|
||||
mutateInboxDetails((prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
view_props: newViewProps,
|
||||
};
|
||||
}, false);
|
||||
|
||||
saveDataToServer(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
inboxId.toString(),
|
||||
newViewProps
|
||||
);
|
||||
}, [inboxId, mutateInboxDetails, projectId, state, workspaceSlug]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "REHYDRATE_THEME",
|
||||
@ -154,6 +187,7 @@ export const InboxViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
||||
value={{
|
||||
filters: state.filters,
|
||||
setFilters,
|
||||
clearAllFilters,
|
||||
}}
|
||||
>
|
||||
<ToastAlert />
|
||||
|
@ -168,7 +168,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
||||
...state,
|
||||
filters: {
|
||||
...state.filters,
|
||||
...payload,
|
||||
...payload?.filters,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { IInboxQueryParams } from "types";
|
||||
import { INBOX_ISSUES } from "constants/fetch-keys";
|
||||
|
||||
const useInboxView = () => {
|
||||
const { filters, setFilters } = useContext(inboxViewContext);
|
||||
const { filters, setFilters, clearAllFilters } = useContext(inboxViewContext);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, inboxId } = router.query;
|
||||
@ -50,10 +50,11 @@ const useInboxView = () => {
|
||||
return {
|
||||
filters,
|
||||
setFilters,
|
||||
clearAllFilters,
|
||||
filtersLength,
|
||||
params,
|
||||
issues: inboxIssues,
|
||||
mutate: mutateInboxIssues,
|
||||
filtersLength,
|
||||
} as const;
|
||||
};
|
||||
|
||||
|
@ -222,7 +222,7 @@ const ProjectInbox: NextPage = () => {
|
||||
}}
|
||||
onDelete={() => setDeleteIssueModal(true)}
|
||||
/>
|
||||
<div className="grid grid-cols-4 flex-1 overflow-auto divide-x divide-brand-base">
|
||||
<div className="grid grid-cols-4 flex-1 divide-x divide-brand-base overflow-hidden">
|
||||
<IssuesListSidebar />
|
||||
<div className="col-span-3 h-full overflow-auto">
|
||||
{inboxIssueId ? (
|
||||
|
Loading…
Reference in New Issue
Block a user