From 45617d145b8589fd5323543cf9ee602b98c6420f Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Tue, 22 Aug 2023 18:45:06 +0530 Subject: [PATCH 1/4] feat: filters in plane deploy implemented multi-level dropdown for plane deploy --- .../[project_slug]/layout.tsx | 4 +- .../[workspace_slug]/[project_slug]/page.tsx | 47 +++++- .../components/issues/filters-render/date.tsx | 38 ----- .../issues/filters-render/index.tsx | 49 ++++-- .../label/filter-label-block.tsx | 18 ++- .../issues/filters-render/label/index.tsx | 21 ++- .../priority/filter-priority-block.tsx | 17 +- .../issues/filters-render/priority/index.tsx | 30 +++- .../state/filter-state-block.tsx | 16 +- .../issues/filters-render/state/index.tsx | 21 ++- apps/space/components/issues/navbar/index.tsx | 12 +- .../issues/navbar/issue-board-view.tsx | 18 ++- .../components/issues/navbar/issue-filter.tsx | 108 ++++++++++++- apps/space/components/ui/dropdown.tsx | 149 ++++++++++++++++++ apps/space/hooks/use-outside-click.tsx | 21 +++ apps/space/lib/mobx/store-init.tsx | 17 ++ apps/space/store/issue.ts | 100 +++++++++++- apps/space/store/types/issue.ts | 19 +++ 18 files changed, 615 insertions(+), 90 deletions(-) delete mode 100644 apps/space/components/issues/filters-render/date.tsx create mode 100644 apps/space/components/ui/dropdown.tsx create mode 100644 apps/space/hooks/use-outside-click.tsx diff --git a/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx b/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx index 7b4ed614233..350295c6a61 100644 --- a/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx +++ b/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx @@ -45,9 +45,7 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => (
- {/*
- -
*/} +
{children}
diff --git a/apps/space/app/[workspace_slug]/[project_slug]/page.tsx b/apps/space/app/[workspace_slug]/[project_slug]/page.tsx index 80dd5f03809..239b769f41d 100644 --- a/apps/space/app/[workspace_slug]/[project_slug]/page.tsx +++ b/apps/space/app/[workspace_slug]/[project_slug]/page.tsx @@ -26,7 +26,14 @@ const WorkspaceProjectPage = observer(() => { const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; const board = - routerSearchparams && routerSearchparams.get("board") != null && (routerSearchparams.get("board") as TIssueBoardKeys | ""); + routerSearchparams && + routerSearchparams.get("board") != null && + (routerSearchparams.get("board") as TIssueBoardKeys | ""); + + const states = routerSearchparams && routerSearchparams.get("states") != null && routerSearchparams.get("states"); + const labels = routerSearchparams && routerSearchparams.get("labels") != null && routerSearchparams.get("labels"); + const priorities = + routerSearchparams && routerSearchparams.get("priorities") != null && routerSearchparams.get("priorities"); // updating default board view when we are in the issues page useEffect(() => { @@ -42,29 +49,53 @@ const WorkspaceProjectPage = observer(() => { if (_key === "gantt" && workspacePRojectSettingViews.gantt === true) userAccessViews.push(_key); }); + let url = `/${workspace_slug}/${project_slug}`; + let _board = board; + if (userAccessViews && userAccessViews.length > 0) { if (!board) { store.issue.setCurrentIssueBoardView(userAccessViews[0]); - router.replace(`/${workspace_slug}/${project_slug}?board=${userAccessViews[0]}`); + _board = userAccessViews[0]; } else { if (userAccessViews.includes(board)) { if (store.issue.currentIssueBoardView === null) store.issue.setCurrentIssueBoardView(board); else { - if (board === store.issue.currentIssueBoardView) - router.replace(`/${workspace_slug}/${project_slug}?board=${board}`); - else { + if (board === store.issue.currentIssueBoardView) { + _board = board; + } else { + _board = board; store.issue.setCurrentIssueBoardView(board); - router.replace(`/${workspace_slug}/${project_slug}?board=${board}`); } } } else { store.issue.setCurrentIssueBoardView(userAccessViews[0]); - router.replace(`/${workspace_slug}/${project_slug}?board=${userAccessViews[0]}`); + _board = userAccessViews[0]; } } } + + _board = _board || "list"; + url = `${url}?board=${_board}`; + + if (states) url = `${url}&states=${states}`; + if (labels) url = `${url}&labels=${labels}`; + if (priorities) url = `${url}&priorities=${priorities}`; + + url = decodeURIComponent(url); + + router.replace(url); } - }, [workspace_slug, project_slug, board, router, store?.issue, store?.project?.workspaceProjectSettings]); + }, [ + workspace_slug, + project_slug, + board, + router, + store?.issue, + store?.project?.workspaceProjectSettings, + states, + labels, + priorities, + ]); useEffect(() => { if (workspace_slug && project_slug) { diff --git a/apps/space/components/issues/filters-render/date.tsx b/apps/space/components/issues/filters-render/date.tsx deleted file mode 100644 index e01d0ae5899..00000000000 --- a/apps/space/components/issues/filters-render/date.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx hook -import { useMobxStore } from "lib/mobx/store-provider"; - -const IssueDateFilter = observer(() => { - const store = useMobxStore(); - - return ( - <> -
-
Due Date
-
- {/*
-
- close -
-
Backlog
-
- close -
-
*/} -
-
- close -
-
- - ); -}); - -export default IssueDateFilter; diff --git a/apps/space/components/issues/filters-render/index.tsx b/apps/space/components/issues/filters-render/index.tsx index 366ae103010..6c4de488bf4 100644 --- a/apps/space/components/issues/filters-render/index.tsx +++ b/apps/space/components/issues/filters-render/index.tsx @@ -1,12 +1,13 @@ "use client"; +import { useRouter, useParams } from "next/navigation"; + // mobx react lite import { observer } from "mobx-react-lite"; // components import IssueStateFilter from "./state"; import IssueLabelFilter from "./label"; import IssuePriorityFilter from "./priority"; -import IssueDateFilter from "./date"; // mobx hook import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -14,24 +15,38 @@ import { RootStore } from "store/root"; const IssueFilter = observer(() => { const store: RootStore = useMobxStore(); - const clearAllFilters = () => {}; + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const clearAllFilters = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "all", + removeAll: true, + }) + ); + }; + + if (store.issue.getIfFiltersIsEmpty()) return null; return ( -
- {/* state */} - {store?.issue?.states && } - {/* labels */} - {store?.issue?.labels && } - {/* priority */} - - {/* due date */} - - {/* clear all filters */} -
-
Clear all filters
+
+
+ {/* state */} + {store.issue.checkIfFilterExistsForKey("state") && } + {/* labels */} + {store.issue.checkIfFilterExistsForKey("label") && } + {/* priority */} + {store.issue.checkIfFilterExistsForKey("priority") && } + {/* clear all filters */} +
+
Clear all filters
+
); diff --git a/apps/space/components/issues/filters-render/label/filter-label-block.tsx b/apps/space/components/issues/filters-render/label/filter-label-block.tsx index 0606bfc9524..559f9f9c080 100644 --- a/apps/space/components/issues/filters-render/label/filter-label-block.tsx +++ b/apps/space/components/issues/filters-render/label/filter-label-block.tsx @@ -1,18 +1,30 @@ "use client"; +import { useRouter, useParams } from "next/navigation"; + // mobx react lite import { observer } from "mobx-react-lite"; // mobx hook import { useMobxStore } from "lib/mobx/store-provider"; // interfaces import { IIssueLabel } from "store/types/issue"; -// constants -import { issueGroupFilter } from "constants/data"; export const RenderIssueLabel = observer(({ label }: { label: IIssueLabel }) => { const store = useMobxStore(); - const removeLabelFromFilter = () => {}; + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const removeLabelFromFilter = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "label", + value: label?.id, + }) + ); + }; return (
{ const store: RootStore = useMobxStore(); - const clearLabelFilters = () => {}; + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const clearLabelFilters = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "label", + removeAll: true, + }) + ); + }; return ( <> @@ -21,7 +35,10 @@ const IssueLabelFilter = observer(() => {
Labels
{store?.issue?.labels && - store?.issue?.labels.map((_label: IIssueLabel, _index: number) => )} + store?.issue?.labels.map( + (_label: IIssueLabel, _index: number) => + store.issue.getUserSelectedFilter("label", _label.id) && + )}
{ const store = useMobxStore(); - const removePriorityFromFilter = () => {}; + const router = useRouter(); + + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const removePriorityFromFilter = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "priority", + value: priority?.key, + }) + ); + }; return (
{ const store = useMobxStore(); + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const clearPriorityFilters = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "priority", + removeAll: true, + }) + ); + }; + return ( <>
Priority
- {issuePriorityFilters.map((_priority: IIssuePriorityFilters, _index: number) => ( - - ))} + {issuePriorityFilters.map( + (_priority: IIssuePriorityFilters, _index: number) => + store.issue.getUserSelectedFilter("priority", _priority.key) && ( + + ) + )}
{ + clearPriorityFilters(); + }} > close
-
{" "} +
); }); diff --git a/apps/space/components/issues/filters-render/state/filter-state-block.tsx b/apps/space/components/issues/filters-render/state/filter-state-block.tsx index 95a4f4c7094..8a015864fc0 100644 --- a/apps/space/components/issues/filters-render/state/filter-state-block.tsx +++ b/apps/space/components/issues/filters-render/state/filter-state-block.tsx @@ -1,5 +1,7 @@ "use client"; +import { useRouter, useParams } from "next/navigation"; + // mobx react lite import { observer } from "mobx-react-lite"; // mobx hook @@ -12,9 +14,21 @@ import { issueGroupFilter } from "constants/data"; export const RenderIssueState = observer(({ state }: { state: IIssueState }) => { const store = useMobxStore(); + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + const stateGroup = issueGroupFilter(state.group); - const removeStateFromFilter = () => {}; + const removeStateFromFilter = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "state", + value: state?.id, + }) + ); + }; if (stateGroup === null) return <>; return ( diff --git a/apps/space/components/issues/filters-render/state/index.tsx b/apps/space/components/issues/filters-render/state/index.tsx index fc73af381f2..f4de23e87ed 100644 --- a/apps/space/components/issues/filters-render/state/index.tsx +++ b/apps/space/components/issues/filters-render/state/index.tsx @@ -1,5 +1,7 @@ "use client"; +import { useRouter, useParams } from "next/navigation"; + // mobx react lite import { observer } from "mobx-react-lite"; // components @@ -13,7 +15,19 @@ import { RootStore } from "store/root"; const IssueStateFilter = observer(() => { const store: RootStore = useMobxStore(); - const clearStateFilters = () => {}; + const router = useRouter(); + const routerParams = useParams(); + + const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string }; + + const clearStateFilters = () => { + router.replace( + store.issue.getURLDefinition(workspace_slug, project_slug, { + key: "state", + removeAll: true, + }) + ); + }; return ( <> @@ -21,7 +35,10 @@ const IssueStateFilter = observer(() => {
State
{store?.issue?.states && - store?.issue?.states.map((_state: IIssueState, _index: number) => )} + store?.issue?.states.map( + (_state: IIssueState, _index: number) => + store.issue.getUserSelectedFilter("state", _state.id) && + )}
{
+ {/* issue filters */} +
+ + {/* */} +
+ {/* issue views */}
- {/* issue filters */} - {/*
- - -
*/} - {/* theming */} {/*
diff --git a/apps/space/components/issues/navbar/issue-board-view.tsx b/apps/space/components/issues/navbar/issue-board-view.tsx index 57c8b27c16f..de46dc96b47 100644 --- a/apps/space/components/issues/navbar/issue-board-view.tsx +++ b/apps/space/components/issues/navbar/issue-board-view.tsx @@ -1,7 +1,7 @@ "use client"; // next imports -import { useRouter, useParams } from "next/navigation"; +import { useRouter, useParams, useSearchParams } from "next/navigation"; // mobx react lite import { observer } from "mobx-react-lite"; // constants @@ -22,7 +22,21 @@ export const NavbarIssueBoardView = observer(() => { const handleCurrentBoardView = (boardView: TIssueBoardKeys) => { store?.issue?.setCurrentIssueBoardView(boardView); - router.replace(`/${workspace_slug}/${project_slug}?board=${boardView}`); + router.replace( + `/${workspace_slug}/${project_slug}?board=${boardView}${ + store?.issue?.userSelectedLabels && store?.issue?.userSelectedLabels.length > 0 + ? `&labels=${store?.issue?.userSelectedLabels.join(",")}` + : "" + }${ + store?.issue?.userSelectedPriorities && store?.issue?.userSelectedPriorities.length > 0 + ? `&priorities=${store?.issue?.userSelectedPriorities.join(",")}` + : "" + }${ + store?.issue?.userSelectedStates && store?.issue?.userSelectedStates.length > 0 + ? `&states=${store?.issue?.userSelectedStates.join(",")}` + : "" + }` + ); }; return ( diff --git a/apps/space/components/issues/navbar/issue-filter.tsx b/apps/space/components/issues/navbar/issue-filter.tsx index 10255882d6e..aa481194a92 100644 --- a/apps/space/components/issues/navbar/issue-filter.tsx +++ b/apps/space/components/issues/navbar/issue-filter.tsx @@ -1,13 +1,119 @@ "use client"; +import { useRouter, usePathname } from "next/navigation"; + +import { ChevronDownIcon } from "@heroicons/react/20/solid"; + // mobx react lite import { observer } from "mobx-react-lite"; // mobx import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +// components +import { Dropdown } from "components/ui/dropdown"; + +// constants +import { issueGroupFilter } from "constants/data"; + +const PRIORITIES = ["urgent", "high", "medium", "low"]; + export const NavbarIssueFilter = observer(() => { const store: RootStore = useMobxStore(); - return
Filter
; + const router = useRouter(); + const pathName = usePathname(); + + const handleOnSelect = (key: "states" | "labels" | "priorities", value: string) => { + if (key === "states") { + store.issue.userSelectedStates = store.issue.userSelectedStates.includes(value) + ? store.issue.userSelectedStates.filter((s) => s !== value) + : [...store.issue.userSelectedStates, value]; + } else if (key === "labels") { + store.issue.userSelectedLabels = store.issue.userSelectedLabels.includes(value) + ? store.issue.userSelectedLabels.filter((l) => l !== value) + : [...store.issue.userSelectedLabels, value]; + } else if (key === "priorities") { + store.issue.userSelectedPriorities = store.issue.userSelectedPriorities.includes(value) + ? store.issue.userSelectedPriorities.filter((p) => p !== value) + : [...store.issue.userSelectedPriorities, value]; + } + + const paramsCommaSeparated = `${`board=${store.issue.currentIssueBoardView || "list"}`}${ + store.issue.userSelectedPriorities.length > 0 ? `&priorities=${store.issue.userSelectedPriorities.join(",")}` : "" + }${store.issue.userSelectedStates.length > 0 ? `&states=${store.issue.userSelectedStates.join(",")}` : ""}${ + store.issue.userSelectedLabels.length > 0 ? `&labels=${store.issue.userSelectedLabels.join(",")}` : "" + }`; + + router.replace(`${pathName}?${paramsCommaSeparated}`); + }; + + return ( + + Filters +