diff --git a/web/components/gantt-chart/chart/header.tsx b/web/components/gantt-chart/chart/header.tsx index e2722b22a..fe4d0c885 100644 --- a/web/components/gantt-chart/chart/header.tsx +++ b/web/components/gantt-chart/chart/header.tsx @@ -15,18 +15,16 @@ type Props = { handleChartView: (view: TGanttViews) => void; handleToday: () => void; loaderTitle: string; - title: string; toggleFullScreenMode: () => void; }; export const GanttChartHeader: React.FC = observer((props) => { - const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props; + const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode } = props; // chart hook const { currentView } = useGanttChart(); return (
-
{title}
{blocks ? `${blocks.length} ${loaderTitle}` : "Loading..."}
diff --git a/web/components/gantt-chart/chart/root.tsx b/web/components/gantt-chart/chart/root.tsx index a4ea8cbf2..395e0771c 100644 --- a/web/components/gantt-chart/chart/root.tsx +++ b/web/components/gantt-chart/chart/root.tsx @@ -172,7 +172,6 @@ export const ChartViewRoot: FC = observer((props) => { handleChartView={(key) => updateCurrentViewRenderPayload(null, key)} handleToday={handleToday} loaderTitle={loaderTitle} - title={title} /> {
+ {canUserCreateModule && (
{canUserCreateIssue && (
+
@@ -217,11 +219,13 @@ export const ModuleCardItem: React.FC = observer((props) => {
{isDateValid ? ( - <> - - {renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"} - - +
+ + {renderFormattedDate(startDate)} + + + {renderFormattedDate(endDate)} +
) : ( No due date )} @@ -229,7 +233,7 @@ export const ModuleCardItem: React.FC = observer((props) => {
-
+
{isEditingAllowed && ( { diff --git a/web/components/modules/module-list-item-action.tsx b/web/components/modules/module-list-item-action.tsx index b34dc7555..fa7d71577 100644 --- a/web/components/modules/module-list-item-action.tsx +++ b/web/components/modules/module-list-item-action.tsx @@ -2,11 +2,11 @@ import React, { FC } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; // icons -import { User2 } from "lucide-react"; +import { CalendarCheck2, CalendarClock, MoveRight, User2 } from "lucide-react"; // types import { IModule } from "@plane/types"; // ui -import { Avatar, AvatarGroup, Tooltip, setPromiseToast } from "@plane/ui"; +import { Tooltip, setPromiseToast } from "@plane/ui"; // components import { FavoriteStar } from "@/components/core"; import { ModuleQuickActions } from "@/components/modules"; @@ -18,7 +18,7 @@ import { EUserProjectRoles } from "@/constants/project"; import { getDate, renderFormattedDate } from "@/helpers/date-time.helper"; // hooks import { useEventTracker, useMember, useModule, useUser } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; +import { ButtonAvatars } from "../dropdowns/member/avatar"; type Props = { moduleId: string; @@ -38,7 +38,6 @@ export const ModuleListItemAction: FC = observer((props) => { const { addModuleToFavorites, removeModuleFromFavorites } = useModule(); const { getUserDetails } = useMember(); const { captureEvent } = useEventTracker(); - const { isMobile } = usePlatformOS(); // derived values const endDate = getDate(moduleDetails.target_date); @@ -109,11 +108,23 @@ export const ModuleListItemAction: FC = observer((props) => { }); }; + const moduleLeadDetails = moduleDetails.lead_id ? getUserDetails(moduleDetails.lead_id) : undefined; + return ( <> + {renderDate && ( +
+ + {renderFormattedDate(startDate)} + + + {renderFormattedDate(endDate)} +
+ )} + {moduleStatus && ( = observer((props) => { )} - {renderDate && ( - - {renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"} + {moduleLeadDetails ? ( + + + ) : ( + + + + + )} - -
- {moduleDetails.member_ids.length > 0 ? ( - - {moduleDetails.member_ids.map((member_id) => { - const member = getUserDetails(member_id); - return ; - })} - - ) : ( - - - - )} -
-
- {isEditingAllowed && !moduleDetails.archived_at && ( { diff --git a/web/components/modules/module-list-item.tsx b/web/components/modules/module-list-item.tsx index 9ad7d2225..b74592112 100644 --- a/web/components/modules/module-list-item.tsx +++ b/web/components/modules/module-list-item.tsx @@ -77,7 +77,7 @@ export const ModuleListItem: React.FC = observer((props) => { ) : progress === 100 ? ( ) : ( - {`${progress}%`} + {`${progress}%`} )} } @@ -89,9 +89,7 @@ export const ModuleListItem: React.FC = observer((props) => { } - actionableItems={ - - } + actionableItems={} isMobile={isMobile} parentRef={parentRef} /> diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index c98772f9b..cfae8a7c1 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -173,7 +173,7 @@ export const NotificationCard: React.FC = (props) => {
{!notification.message ? ( -
+
{notificationTriggeredBy.is_bot ? notificationTriggeredBy.first_name diff --git a/web/components/project/card.tsx b/web/components/project/card.tsx index 5e94b1fb5..8f29b245b 100644 --- a/web/components/project/card.tsx +++ b/web/components/project/card.tsx @@ -2,7 +2,7 @@ import React, { useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import Link from "next/link"; import { useRouter } from "next/router"; -import { ArchiveRestoreIcon, Check, ExternalLink, LinkIcon, Lock, Pencil, Trash2, UserPlus } from "lucide-react"; +import { ArchiveRestoreIcon, Check, ExternalLink, LinkIcon, Lock, Settings, Trash2, UserPlus } from "lucide-react"; // types import type { IProject } from "@plane/types"; // ui @@ -105,10 +105,10 @@ export const ProjectCard: React.FC = observer((props) => { const MENU_ITEMS: TContextMenuItem[] = [ { - key: "edit", + key: "settings", action: () => router.push(`/${workspaceSlug}/projects/${project.id}/settings`), - title: "Edit", - icon: Pencil, + title: "Settings", + icon: Settings, shouldRender: !isArchived && (isOwner || isMember), }, { @@ -322,7 +322,7 @@ export const ProjectCard: React.FC = observer((props) => { }} href={`/${workspaceSlug}/projects/${project.id}/settings`} > - + ) : ( diff --git a/web/components/views/index.ts b/web/components/views/index.ts index 2a2a02dc7..085a1c6b7 100644 --- a/web/components/views/index.ts +++ b/web/components/views/index.ts @@ -5,3 +5,4 @@ export * from "./quick-actions"; export * from "./view-list-item"; export * from "./views-list"; export * from "./view-list-item-action"; +export * from "./view-list-header"; diff --git a/web/components/views/view-list-header.tsx b/web/components/views/view-list-header.tsx new file mode 100644 index 000000000..6d36dfef9 --- /dev/null +++ b/web/components/views/view-list-header.tsx @@ -0,0 +1,84 @@ +import React, { useRef, useState } from "react"; +import { observer } from "mobx-react"; +// icons +import { Search, X } from "lucide-react"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useProjectView } from "@/hooks/store"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; + +export const ViewListHeader = observer(() => { + // states + const [isSearchOpen, setIsSearchOpen] = useState(false); + // const [isSearchOpen, setIsSearchOpen] = useState(searchQuery !== "" ? true : false); + // refs + const inputRef = useRef(null); + + const { searchQuery, updateSearchQuery } = useProjectView(); + + // handlers + const handleInputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") { + if (searchQuery && searchQuery.trim() !== "") updateSearchQuery(""); + else { + setIsSearchOpen(false); + inputRef.current?.blur(); + } + } + }; + + // outside click detector hook + useOutsideClickDetector(inputRef, () => { + if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); + }); + + return ( +
+
+ {!isSearchOpen && ( + + )} +
+ + 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/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 85be0c140..08e621d0f 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -1,20 +1,23 @@ import { ReactElement, useCallback } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; +// types import { TModuleFilters } from "@plane/types"; -// layouts // components import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; import { ModulesListHeader } from "@/components/headers"; -import { ModuleViewHeader, ModuleAppliedFiltersList, ModulesListView } from "@/components/modules"; -// types -// hooks +import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules"; import ModulesListMobileHeader from "@/components/modules/moduels-list-mobile-header"; +// constants import { EmptyStateType } from "@/constants/empty-state"; +// helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; +// hooks import { useModuleFilter, useProject } from "@/hooks/store"; +// layouts import { AppLayout } from "@/layouts/app-layout"; +// types import { NextPageWithLayout } from "@/lib/types"; const ProjectModulesPage: NextPageWithLayout = observer(() => { @@ -58,12 +61,6 @@ const ProjectModulesPage: NextPageWithLayout = observer(() => { <>
-
-
- Module name -
- -
{(calculateTotalFilters(currentProjectFilters ?? {}) !== 0 || currentProjectDisplayFilters?.favorites) && (
; // 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