From e396424db793bbb71041dd23eff2ae7d3b94cc2c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 10 May 2024 15:19:05 +0530 Subject: [PATCH] [WEB-1251] chore: view list enhancement (#4427) * chore: moved search query to mobx store * chore: moved view sub-header to app header * chore: created by avatar added in view item list --- web/components/headers/project-views.tsx | 2 + web/components/views/index.ts | 1 + web/components/views/view-list-header.tsx | 84 +++++++++++++++++++ .../views/view-list-item-action.tsx | 10 ++- web/components/views/views-list.tsx | 82 +----------------- web/store/project-view.store.ts | 12 +++ 6 files changed, 109 insertions(+), 82 deletions(-) create mode 100644 web/components/views/view-list-header.tsx diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx index b733e6784..c79934aec 100644 --- a/web/components/headers/project-views.tsx +++ b/web/components/headers/project-views.tsx @@ -7,6 +7,7 @@ import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // helpers import { ProjectLogo } from "@/components/project"; +import { ViewListHeader } from "@/components/views"; import { EUserProjectRoles } from "@/constants/project"; // constants import { useCommandPalette, useProject, useUser } from "@/hooks/store"; @@ -58,6 +59,7 @@ export const ProjectViewsHeader: React.FC = observer(() => { {canUserCreateIssue && (
+
+ )} +
+ + updateSearchQuery(e.target.value)} + onKeyDown={handleInputKeyDown} + /> + {isSearchOpen && ( + + )} +
+
+
+ ); +}); diff --git a/web/components/views/view-list-item-action.tsx b/web/components/views/view-list-item-action.tsx index 563d1da41..80ba5ba0c 100644 --- a/web/components/views/view-list-item-action.tsx +++ b/web/components/views/view-list-item-action.tsx @@ -11,7 +11,8 @@ import { EUserProjectRoles } from "@/constants/project"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useProjectView, useUser } from "@/hooks/store"; +import { useMember, useProjectView, useUser } from "@/hooks/store"; +import { ButtonAvatars } from "../dropdowns/member/avatar"; type Props = { parentRef: React.RefObject; @@ -31,6 +32,7 @@ export const ViewListItemAction: FC = observer((props) => { membership: { currentProjectRole }, } = useUser(); const { addViewToFavorites, removeViewFromFavorites } = useProjectView(); + const { getUserDetails } = useMember(); // derived values const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; @@ -50,6 +52,8 @@ export const ViewListItemAction: FC = observer((props) => { removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id); }; + const createdByDetails = view.created_by ? getUserDetails(view.created_by) : undefined; + return ( <> {workspaceSlug && projectId && view && ( @@ -65,6 +69,10 @@ export const ViewListItemAction: FC = observer((props) => {

{totalFilters} {totalFilters === 1 ? "filter" : "filters"}

+ + {/* created by */} + {createdByDetails && } + {isEditingAllowed && ( { diff --git a/web/components/views/views-list.tsx b/web/components/views/views-list.tsx index 967469c79..ea300678a 100644 --- a/web/components/views/views-list.tsx +++ b/web/components/views/views-list.tsx @@ -1,7 +1,4 @@ -import { useRef, useState } from "react"; import { observer } from "mobx-react-lite"; -// ui -import { Search, X } from "lucide-react"; // components import { ListLayout } from "@/components/core/list"; import { EmptyState } from "@/components/empty-state"; @@ -9,28 +6,13 @@ import { ViewListLoader } from "@/components/ui"; import { ProjectViewListItem } from "@/components/views"; // constants import { EmptyStateType } from "@/constants/empty-state"; -// helper -import { cn } from "@/helpers/common.helper"; // hooks import { useCommandPalette, useProjectView } from "@/hooks/store"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; export const ProjectViewsList = observer(() => { - // states - const [searchQuery, setSearchQuery] = useState(""); - const [isSearchOpen, setIsSearchOpen] = useState(searchQuery !== "" ? true : false); - - // refs - const inputRef = useRef(null); - // store hooks const { toggleCreateViewModal } = useCommandPalette(); - const { projectViewIds, getViewById, loader } = useProjectView(); - - // outside click detector hook - useOutsideClickDetector(inputRef, () => { - if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); - }); + const { projectViewIds, getViewById, loader, searchQuery } = useProjectView(); if (loader || !projectViewIds) return ; @@ -39,72 +21,10 @@ export const ProjectViewsList = observer(() => { const filteredViewsList = viewsList.filter((v) => v?.name.toLowerCase().includes(searchQuery.toLowerCase())); - // handlers - const handleInputKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Escape") { - if (searchQuery && searchQuery.trim() !== "") setSearchQuery(""); - else { - setIsSearchOpen(false); - inputRef.current?.blur(); - } - } - }; - return ( <> {viewsList.length > 0 ? (
-
-
- Project Views -
-
-
- {!isSearchOpen && ( - - )} -
- - setSearchQuery(e.target.value)} - onKeyDown={handleInputKeyDown} - /> - {isSearchOpen && ( - - )} -
-
-
-
{filteredViewsList.length > 0 ? ( filteredViewsList.map((view) => ) diff --git a/web/store/project-view.store.ts b/web/store/project-view.store.ts index 95ecece58..92b8719c1 100644 --- a/web/store/project-view.store.ts +++ b/web/store/project-view.store.ts @@ -12,11 +12,13 @@ export interface IProjectViewStore { loader: boolean; fetchedMap: Record; // observables + searchQuery: string; viewMap: Record; // computed projectViewIds: string[] | null; // computed actions getViewById: (viewId: string) => IProjectView; + updateSearchQuery: (query: string) => void; // fetch actions fetchViews: (workspaceSlug: string, projectId: string) => Promise; fetchViewDetails: (workspaceSlug: string, projectId: string, viewId: string) => Promise; @@ -38,6 +40,7 @@ export class ProjectViewStore implements IProjectViewStore { // observables loader: boolean = false; viewMap: Record = {}; + searchQuery: string = ""; //loaders fetchedMap: Record = {}; // root store @@ -51,6 +54,7 @@ export class ProjectViewStore implements IProjectViewStore { loader: observable.ref, viewMap: observable, fetchedMap: observable, + searchQuery: observable.ref, // computed projectViewIds: computed, // fetch actions @@ -60,6 +64,8 @@ export class ProjectViewStore implements IProjectViewStore { createView: action, updateView: action, deleteView: action, + // actions + updateSearchQuery: action, // favorites actions addViewToFavorites: action, removeViewFromFavorites: action, @@ -85,6 +91,12 @@ export class ProjectViewStore implements IProjectViewStore { */ getViewById = computedFn((viewId: string) => this.viewMap?.[viewId] ?? null); + /** + * @description update search query + * @param {string} query + */ + updateSearchQuery = (query: string) => (this.searchQuery = query); + /** * Fetches views for current project * @param workspaceSlug