From 5d9393cfa68ce2291855a85a7be7c9828de5d79b Mon Sep 17 00:00:00 2001 From: gurusainath Date: Sat, 10 Feb 2024 14:15:57 +0530 Subject: [PATCH] chore: filter saving store and ui updates --- .../view/display-filters/dropdown.tsx | 21 +++++- web/components/view/helpers/filters.tsx | 49 ------------- web/components/view/index.ts | 9 ++- web/components/view/layout.tsx | 4 +- .../view/{all-issues-root.tsx => root.tsx} | 59 +++++++++------- web/components/view/views/edit-dropdown.tsx | 41 +++++++++++ ...opdown-item.tsx => view-dropdown-item.tsx} | 0 .../{dropdown/root.tsx => view-dropdown.tsx} | 27 +++++-- .../[workspaceSlug]/views/public/[viewId].tsx | 4 +- web/store/view/view.store.ts | 70 +++++++++++-------- yarn.lock | 18 +++-- 11 files changed, 176 insertions(+), 126 deletions(-) delete mode 100644 web/components/view/helpers/filters.tsx rename web/components/view/{all-issues-root.tsx => root.tsx} (84%) create mode 100644 web/components/view/views/edit-dropdown.tsx rename web/components/view/views/{dropdown/dropdown-item.tsx => view-dropdown-item.tsx} (100%) rename web/components/view/views/{dropdown/root.tsx => view-dropdown.tsx} (87%) diff --git a/web/components/view/display-filters/dropdown.tsx b/web/components/view/display-filters/dropdown.tsx index bc8d737c0..facbf29d1 100644 --- a/web/components/view/display-filters/dropdown.tsx +++ b/web/components/view/display-filters/dropdown.tsx @@ -2,6 +2,7 @@ import { FC, Fragment, ReactNode, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; +import { Placement } from "@popperjs/core"; import { MonitorDot } from "lucide-react"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; @@ -21,10 +22,20 @@ type TViewDisplayFiltersDropdown = { viewOperations: TViewOperations; children?: ReactNode; displayDropdownText?: boolean; + dropdownPlacement?: Placement; }; export const ViewDisplayFiltersDropdown: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + viewOperations, + children, + displayDropdownText = true, + dropdownPlacement = "bottom-start", + } = props; // state const [dropdownToggle, setDropdownToggle] = useState(false); // refs @@ -34,7 +45,7 @@ export const ViewDisplayFiltersDropdown: FC = obser const [popperElement, setPopperElement] = useState(null); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: "bottom-start", + placement: dropdownPlacement, modifiers: [ { name: "preventOverflow", @@ -42,6 +53,12 @@ export const ViewDisplayFiltersDropdown: FC = obser padding: 12, }, }, + { + name: "offset", + options: { + offset: [0, 10], + }, + }, ], }); diff --git a/web/components/view/helpers/filters.tsx b/web/components/view/helpers/filters.tsx deleted file mode 100644 index 190a4828b..000000000 --- a/web/components/view/helpers/filters.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// hooks -import { useProject, useProjectState, useMember } from "hooks/store"; -// types -import { TViewFilters } from "@plane/types"; - -type TFilterPropertyItemByFilterKeyAndId = { - key: keyof TViewFilters; - id: string; - icon: string; - title: string; -}; - -export const filterPropertyItemByFilterKeyAndId = ( - key: keyof TViewFilters, - id: string -): TFilterPropertyItemByFilterKeyAndId | undefined => { - if (!key || id) return undefined; - - switch (key) { - case "project": - return undefined; // store - case "module": - return undefined; // store - case "cycle": - return undefined; // store - case "priority": - return undefined; // constant - case "state": - return undefined; // store - case "state_group": - return undefined; // constant - case "assignees": - return undefined; // store -> workspace and project level - case "mentions": - return undefined; // store -> workspace and project level - case "subscriber": - return undefined; // store -> workspace and project level - case "created_by": - return undefined; // store -> workspace and project level - case "labels": - return undefined; // store -> workspace and project level - case "start_date": - return undefined; // constants - case "target_date": - return undefined; // constants - default: - return undefined; - } -}; diff --git a/web/components/view/index.ts b/web/components/view/index.ts index 8601dd210..32e82b957 100644 --- a/web/components/view/index.ts +++ b/web/components/view/index.ts @@ -1,13 +1,12 @@ -export * from "./all-issues-root"; +export * from "./root"; // views export * from "./views/root"; export * from "./views/view-item"; - -export * from "./views/dropdown/root"; -export * from "./views/dropdown/dropdown-item"; - +export * from "./views/view-dropdown"; +export * from "./views/view-dropdown-item"; export * from "./views/create-edit-form"; +export * from "./views/edit-dropdown"; // layouts export * from "./layout"; diff --git a/web/components/view/layout.tsx b/web/components/view/layout.tsx index c68dc9d01..d2e75abb2 100644 --- a/web/components/view/layout.tsx +++ b/web/components/view/layout.tsx @@ -6,7 +6,7 @@ import { useViewDetail } from "hooks/store"; // ui import { Tooltip } from "@plane/ui"; // types -import { TViewTypes } from "@plane/types"; +import { TViewLayouts, TViewTypes } from "@plane/types"; import { TViewOperations } from "./types"; type TViewLayoutRoot = { @@ -17,7 +17,7 @@ type TViewLayoutRoot = { viewOperations: TViewOperations; }; -const LAYOUTS_DATA: { key: string; title: string; icon: LucideIcon }[] = [ +const LAYOUTS_DATA: { key: TViewLayouts; title: string; icon: LucideIcon }[] = [ { key: "list", title: "List Layout", icon: List }, { key: "kanban", title: "Kanban Layout", icon: Kanban }, { key: "calendar", title: "Calendar Layout", icon: Calendar }, diff --git a/web/components/view/all-issues-root.tsx b/web/components/view/root.tsx similarity index 84% rename from web/components/view/all-issues-root.tsx rename to web/components/view/root.tsx index 178498280..00de94643 100644 --- a/web/components/view/all-issues-root.tsx +++ b/web/components/view/root.tsx @@ -1,7 +1,7 @@ import { FC, Fragment, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; -import { CheckCircle, ChevronDown, ChevronUp, Pencil } from "lucide-react"; +import { CheckCircle, Pencil } from "lucide-react"; // hooks import { useView, useViewDetail } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -15,6 +15,7 @@ import { ViewAppliedFiltersRoot, ViewDuplicateConfirmationModal, ViewDeleteConfirmationModal, + ViewEditDropdown, } from "."; // ui import { Spinner } from "@plane/ui"; @@ -24,7 +25,7 @@ import { viewLocalPayload } from "constants/view"; import { TViewOperations } from "./types"; import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties, TViewTypes } from "@plane/types"; -type TAllIssuesViewRoot = { +type TGlobalViewRoot = { workspaceSlug: string; projectId: string | undefined; viewId: string; @@ -38,7 +39,7 @@ type TViewOperationsToggle = { viewId: string | undefined; }; -export const AllIssuesViewRoot: FC = observer((props) => { +export const GlobalViewRoot: FC = observer((props) => { const { workspaceSlug, projectId, viewId, viewType, baseRoute, workspaceViewTabOptions } = props; // hooks const viewStore = useView(workspaceSlug, projectId, viewType); @@ -112,7 +113,7 @@ export const AllIssuesViewRoot: FC = observer((props) => { [viewStore, viewDetailStore, setToastAlert, viewOperationsToggle, viewDetailCreateStore] ); - // fetch all issues + // fetch all views useEffect(() => { const fetchViews = async () => { await viewStore?.fetch(viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader"); @@ -130,28 +131,33 @@ export const AllIssuesViewRoot: FC = observer((props) => { return (
-
-
+
+
-
All Issues
+
+ All Issues +
-
- {workspaceViewTabOptions.map((tab) => ( - +
+ {workspaceViewTabOptions.map((tab) => ( + - {tab.title} - - ))} + > + {tab.title} + + ))} +
@@ -201,7 +207,7 @@ export const AllIssuesViewRoot: FC = observer((props) => { viewId={viewId} viewType={viewType} viewOperations={viewOperations} - displayDropdownText={true} + displayDropdownText={false} />
@@ -212,7 +218,7 @@ export const AllIssuesViewRoot: FC = observer((props) => { viewId={viewId} viewType={viewType} viewOperations={viewOperations} - displayDropdownText={true} + displayDropdownText={false} />
@@ -222,14 +228,13 @@ export const AllIssuesViewRoot: FC = observer((props) => {
-
-
- Update -
-
- -
-
+ )} diff --git a/web/components/view/views/edit-dropdown.tsx b/web/components/view/views/edit-dropdown.tsx new file mode 100644 index 000000000..dba74f8d2 --- /dev/null +++ b/web/components/view/views/edit-dropdown.tsx @@ -0,0 +1,41 @@ +import { FC } from "react"; +import { ChevronDown } from "lucide-react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useViewDetail } from "hooks/store"; +// components +// types +import { TViewTypes } from "@plane/types"; +import { TViewOperations } from "../types"; + +type TViewEditDropdown = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + viewOperations: TViewOperations; +}; + +export const ViewEditDropdown: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + + if (!viewDetailStore?.isFiltersUpdateEnabled) return <>; + return ( + <> +
+ +
+ +
+
+ + ); +}); diff --git a/web/components/view/views/dropdown/dropdown-item.tsx b/web/components/view/views/view-dropdown-item.tsx similarity index 100% rename from web/components/view/views/dropdown/dropdown-item.tsx rename to web/components/view/views/view-dropdown-item.tsx diff --git a/web/components/view/views/dropdown/root.tsx b/web/components/view/views/view-dropdown.tsx similarity index 87% rename from web/components/view/views/dropdown/root.tsx rename to web/components/view/views/view-dropdown.tsx index 04a6e17c3..22f3bde13 100644 --- a/web/components/view/views/dropdown/root.tsx +++ b/web/components/view/views/view-dropdown.tsx @@ -1,15 +1,16 @@ import { FC, Fragment, ReactNode, useRef, useState } from "react"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; +import { Placement } from "@popperjs/core"; import { Plus, Search } from "lucide-react"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; import { useView } from "hooks/store"; // components -import { ViewDropdownItem } from "../../"; +import { ViewDropdownItem } from ".."; // types import { TViewTypes } from "@plane/types"; -import { TViewOperations } from "../../types"; +import { TViewOperations } from "../types"; type TViewDropdown = { workspaceSlug: string; @@ -19,10 +20,20 @@ type TViewDropdown = { viewOperations: TViewOperations; children?: ReactNode; baseRoute: string; + dropdownPlacement?: Placement; }; export const ViewDropdown: FC = (props) => { - const { workspaceSlug, projectId, viewId: currentViewId, viewType, viewOperations, children, baseRoute } = props; + const { + workspaceSlug, + projectId, + viewId: currentViewId, + viewType, + viewOperations, + children, + baseRoute, + dropdownPlacement = "bottom-start", + } = props; // hooks const viewStore = useView(workspaceSlug, projectId, viewType); // states @@ -35,7 +46,7 @@ export const ViewDropdown: FC = (props) => { const [popperElement, setPopperElement] = useState(null); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: "bottom-start", + placement: dropdownPlacement, modifiers: [ { name: "preventOverflow", @@ -43,6 +54,12 @@ export const ViewDropdown: FC = (props) => { padding: 12, }, }, + { + name: "offset", + options: { + offset: [0, 10], + }, + }, ], }); @@ -80,7 +97,7 @@ export const ViewDropdown: FC = (props) => { ref={setPopperElement} style={styles.popper} {...attributes.popper} - className="my-1 w-64 p-2 space-y-2 rounded bg-custom-background-100 border-[0.5px] border-custom-border-300 shadow-custom-shadow-rg focus:outline-none" + className="w-64 p-2 space-y-2 rounded bg-custom-background-100 border-[0.5px] border-custom-border-300 shadow-custom-shadow-rg focus:outline-none" >
diff --git a/web/pages/[workspaceSlug]/views/public/[viewId].tsx b/web/pages/[workspaceSlug]/views/public/[viewId].tsx index 7d1aa959b..6226492b2 100644 --- a/web/pages/[workspaceSlug]/views/public/[viewId].tsx +++ b/web/pages/[workspaceSlug]/views/public/[viewId].tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; // layouts import { AppLayout } from "layouts/app-layout"; // components -import { AllIssuesViewRoot } from "components/view"; +import { GlobalViewRoot } from "components/view"; // types import { NextPageWithLayout } from "lib/types"; // constants @@ -33,7 +33,7 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => { return (
- void; setDescription: (description: string) => void; @@ -37,11 +39,11 @@ export type TViewStore = TView & { resetChanges: () => void; saveChanges: () => Promise; // actions + update: (viewData: Partial) => Promise; lockView: () => Promise; unlockView: () => Promise; makeFavorite: () => Promise; removeFavorite: () => Promise; - update: (viewData: Partial) => Promise; }; export class ViewStore extends FiltersHelper implements TViewStore { @@ -102,10 +104,14 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.updated_by = _view.updated_by; this.created_at = _view.created_at; this.updated_at = _view.updated_at; - this.is_local_view = _view.is_local_view; this.is_create = _view.is_create; this.is_editable = _view.is_editable; + this.filtersToUpdate = { + filters: this.computedFilters(_view.filters), + display_filters: this.computedDisplayFilters(_view.display_filters), + display_properties: this.computedDisplayProperties(_view.display_properties), + }; makeObservable(this, { // observables @@ -137,6 +143,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { appliedFilters: computed, appliedFiltersQueryParams: computed, isFiltersApplied: computed, + isFiltersUpdateEnabled: computed, // helper actions setName: action, setFilters: action, @@ -148,6 +155,8 @@ export class ViewStore extends FiltersHelper implements TViewStore { update: action, lockView: action, unlockView: action, + makeFavorite: action, + removeFavorite: action, }); } @@ -179,6 +188,17 @@ export class ViewStore extends FiltersHelper implements TViewStore { return isFiltersApplied; } + get isFiltersUpdateEnabled() { + const filters = this.filters; + const appliedFilters = this.appliedFilters?.filters; + let isFiltersUpdateEnabled = false; + Object.keys(appliedFilters).forEach((key) => { + const _key = key as keyof TViewFilters; + if (!isEqual(appliedFilters[_key].slice().sort(), filters[_key].slice().sort())) isFiltersUpdateEnabled = true; + }); + return isFiltersUpdateEnabled; + } + // helper actions setName = (name: string) => { runInAction(() => { @@ -194,10 +214,8 @@ export class ViewStore extends FiltersHelper implements TViewStore { setFilters = (filterKey: keyof TViewFilters | undefined = undefined, filterValue: "clear_all" | string) => { runInAction(() => { - this.loader = "submit"; if (filterKey === undefined) { if (filterValue === "clear_all") set(this.filtersToUpdate, ["filters"], {}); - this.loader = undefined; } else update(this.filtersToUpdate, ["filters", filterKey], (_values = []) => { if (filterValue === "clear_all") return []; @@ -238,7 +256,6 @@ export class ViewStore extends FiltersHelper implements TViewStore { resetChanges = () => { runInAction(() => { - this.loader = undefined; this.filtersToUpdate = { name: this.name, description: this.description, @@ -251,11 +268,8 @@ export class ViewStore extends FiltersHelper implements TViewStore { saveChanges = async () => { try { - this.loader = "submitting"; if (this.filtersToUpdate) await this.update(this.filtersToUpdate); - this.loader = undefined; } catch { - this.loader = undefined; Object.keys(this.filtersToUpdate).forEach((key) => { const _key = key as keyof TView; set(this, _key, this.filtersToUpdate[_key]); @@ -264,6 +278,25 @@ export class ViewStore extends FiltersHelper implements TViewStore { }; // actions + update = async (viewData: Partial) => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id) return; + + const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); + if (!view) return; + + runInAction(() => { + Object.keys(viewData).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, viewData[_key]); + }); + }); + } catch { + this.resetChanges(); + } + }; + lockView = async () => { try { const { workspaceSlug, projectId } = this.store.app.router; @@ -327,23 +360,4 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.is_favorite = this.is_favorite; } }; - - update = async (viewData: Partial) => { - try { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id) return; - - const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); - if (!view) return; - - runInAction(() => { - Object.keys(viewData).forEach((key) => { - const _key = key as keyof TView; - set(this, _key, viewData[_key]); - }); - }); - } catch { - this.resetChanges(); - } - }; } diff --git a/yarn.lock b/yarn.lock index 291c710bd..4e6a36ba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5012,7 +5012,7 @@ fault@^2.0.0: dependencies: format "^0.2.0" -fflate@^0.4.1: +fflate@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== @@ -7171,12 +7171,18 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: picocolors "^1.0.0" source-map-js "^1.0.2" -posthog-js@^1.88.4: - version "1.96.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.96.1.tgz#4f9719a24e4e14037b0e72d430194d7cdb576447" - integrity sha512-kv1vQqYMt2BV3YHS+wxsbGuP+tz+M3y1AzNhz8TfkpY1HT8W/ONT0i0eQpeRr9Y+d4x/fZ6M4cXG5GMvi9lRCA== +posthog-js@^1.105.0: + version "1.105.6" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.105.6.tgz#3544de4389d5c7743fa420178bd127e49c4dc825" + integrity sha512-5ITXsh29XIuNohHLy21nawGnfFZDpyt+yfnWge9sJl5yv0nNuoUmLiDgw1tJafoqGrfd5CUasKyzSI21HxsSeQ== dependencies: - fflate "^0.4.1" + fflate "^0.4.8" + preact "^10.19.3" + +preact@^10.19.3: + version "10.19.4" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.4.tgz#735d331d5b1bd2182cc36f2ba481fd6f0da3fe3b" + integrity sha512-dwaX5jAh0Ga8uENBX1hSOujmKWgx9RtL80KaKUFLc6jb4vCEAc3EeZ0rnQO/FO4VgjfPMfoLFWnNG8bHuZ9VLw== prebuild-install@^7.1.1: version "7.1.1"