diff --git a/packages/types/src/dashboard.d.ts b/packages/types/src/dashboard.d.ts index b42b5506fb..d96523280f 100644 --- a/packages/types/src/dashboard.d.ts +++ b/packages/types/src/dashboard.d.ts @@ -186,3 +186,41 @@ export type THomeDashboardResponse = { dashboard: TDashboard; widgets: TWidget[]; }; + +// home +type TPageEntityData = { + id: string; + name: string; + logo_props: TLogoProps; + project_id: string; + owned_by: string; + project_identifier: string; +}; + +type TProjectEntityData = { + id: string; + name: string; + logo_props: TLogoProps; + project_members: ProjectMember[]; + identifier: string; +}; + +type TIssueEntityData = { + id: string; + name: string; + state: string; + priority: TIssuePriorities; + assignees: string[]; + type: string | null; + sequence_id: number; + project_id: string; + project_identifier: string; +}; + +type TActivityEntityData = { + id: string; + entity_name: "page" | "project" | "issue"; + entity_identifier: string; + visited_at: string; + entity_data: TPageEntityData | TProjectEntityData | TIssueEntityData; +}; diff --git a/web/core/components/home/home-dashboard-widgets.tsx b/web/core/components/home/home-dashboard-widgets.tsx index 69863972b0..1e910082ca 100644 --- a/web/core/components/home/home-dashboard-widgets.tsx +++ b/web/core/components/home/home-dashboard-widgets.tsx @@ -3,20 +3,20 @@ import { useParams } from "next/navigation"; // types import { TWidgetKeys, WidgetProps } from "@plane/types"; // components -import { RecentActivityWidget, EmptyWorkspace } from "@/components/dashboard"; // hooks import { useDashboard, useProject } from "@/hooks/store"; import { useHome } from "@/hooks/store/use-home"; import { HomePageHeader } from "@/plane-web/components/home/header"; -import { DashboardQuickLinks } from "./links"; +import { RecentActivityWidget } from "./widgets"; +import { DashboardQuickLinks } from "./widgets/links"; import { ManageWidgetsModal } from "./widgets/manage"; +import { StickiesWidget } from "@/plane-web/components/stickies"; const WIDGETS_LIST: { - [key in TWidgetKeys]: { component: React.FC; fullWidth: boolean }; + [key: string]: { component: React.FC; fullWidth: boolean }; } = { recent_activity: { component: RecentActivityWidget, fullWidth: false }, - // recent_collaborators: { component: RecentCollaboratorsWidget, fullWidth: true }, - // my_stickies: { component: StickiesWidget, fullWidth: false }, + stickies: { component: StickiesWidget, fullWidth: false }, }; export const DashboardWidgets = observer(() => { @@ -42,25 +42,20 @@ export const DashboardWidgets = observer(() => { /> - {totalProjectIds?.length === 0 ? ( - - ) : ( - Object.entries(WIDGETS_LIST).map(([key, widget]) => { - const WidgetComponent = widget.component; - // if the widget doesn't exist, return null - // if (!doesWidgetExist(key as TWidgetKeys)) return null; - // if the widget is full width, return it in a 2 column grid - console.log({ widget, key }); - if (widget.fullWidth) - return ( -
- -
- ); - else - return ; - }) - )} + {Object.entries(WIDGETS_LIST).map(([key, widget]) => { + const WidgetComponent = widget.component; + // if the widget doesn't exist, return null + // if (!doesWidgetExist(key as TWidgetKeys)) return null; + // if the widget is full width, return it in a 2 column grid + if (widget.fullWidth) + return ( +
+ +
+ ); + else + return ; + })} ); }); diff --git a/web/core/components/home/widgets/empty-states/root.tsx b/web/core/components/home/widgets/empty-states/root.tsx index fe6c8e5f3b..06606f367d 100644 --- a/web/core/components/home/widgets/empty-states/root.tsx +++ b/web/core/components/home/widgets/empty-states/root.tsx @@ -42,7 +42,7 @@ export const EmptyWorkspace = () => { icon: , cta: { text: "Invite now", - link: "settings/members", + link: `/${workspaceSlug}/settings/members`, }, }, { diff --git a/web/core/components/home/links/action.tsx b/web/core/components/home/widgets/links/action.tsx similarity index 100% rename from web/core/components/home/links/action.tsx rename to web/core/components/home/widgets/links/action.tsx diff --git a/web/core/components/home/links/create-update-link-modal.tsx b/web/core/components/home/widgets/links/create-update-link-modal.tsx similarity index 100% rename from web/core/components/home/links/create-update-link-modal.tsx rename to web/core/components/home/widgets/links/create-update-link-modal.tsx diff --git a/web/core/components/home/links/index.ts b/web/core/components/home/widgets/links/index.ts similarity index 100% rename from web/core/components/home/links/index.ts rename to web/core/components/home/widgets/links/index.ts diff --git a/web/core/components/home/links/link-detail.tsx b/web/core/components/home/widgets/links/link-detail.tsx similarity index 96% rename from web/core/components/home/links/link-detail.tsx rename to web/core/components/home/widgets/links/link-detail.tsx index cbea44c8b5..26aa4f9c8b 100644 --- a/web/core/components/home/links/link-detail.tsx +++ b/web/core/components/home/widgets/links/link-detail.tsx @@ -80,7 +80,7 @@ export const ProjectLinkDetail: FC = observer((props) => {
{linkDetail.title || linkDetail.url}
-
{calculateTimeAgo(linkDetail.created_at)}
+
{calculateTimeAgo(linkDetail.created_at)}
; export type TProjectLinkList = { linkOperations: TLinkOperationsModal; - workspaceSlug: string; }; @@ -50,7 +50,7 @@ export const ProjectLinkList: FC = observer((props) => { return () => window.removeEventListener("resize", updateColumnCount); }, []); - if (!links) return <>; + if (links === undefined) return ; return (
diff --git a/web/core/components/home/links/root.tsx b/web/core/components/home/widgets/links/root.tsx similarity index 96% rename from web/core/components/home/links/root.tsx rename to web/core/components/home/widgets/links/root.tsx index 7a30c0dbe4..1b2b389709 100644 --- a/web/core/components/home/links/root.tsx +++ b/web/core/components/home/widgets/links/root.tsx @@ -33,7 +33,7 @@ export const DashboardQuickLinks = observer((props: TProps) => { preloadedData={linkData} setLinkData={setLinkData} /> -
+
{/* rendering links */}
diff --git a/web/core/components/home/links/use-links.tsx b/web/core/components/home/widgets/links/use-links.tsx similarity index 100% rename from web/core/components/home/links/use-links.tsx rename to web/core/components/home/widgets/links/use-links.tsx diff --git a/web/core/components/home/widgets/loaders/assigned-issues.tsx b/web/core/components/home/widgets/loaders/assigned-issues.tsx deleted file mode 100644 index 8a78fedf1a..0000000000 --- a/web/core/components/home/widgets/loaders/assigned-issues.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; - -// ui -import { Loader } from "@plane/ui"; - -export const AssignedIssuesWidgetLoader = () => ( - -
- - -
-
- - -
-
- - - - - -
-
-); diff --git a/web/core/components/home/widgets/loaders/issues-by-priority.tsx b/web/core/components/home/widgets/loaders/issues-by-priority.tsx deleted file mode 100644 index c6f075b580..0000000000 --- a/web/core/components/home/widgets/loaders/issues-by-priority.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -// ui -import { Loader } from "@plane/ui"; - -export const IssuesByPriorityWidgetLoader = () => ( - - -
- - - - - -
-
-); diff --git a/web/core/components/home/widgets/loaders/issues-by-state-group.tsx b/web/core/components/home/widgets/loaders/issues-by-state-group.tsx deleted file mode 100644 index 099323cce7..0000000000 --- a/web/core/components/home/widgets/loaders/issues-by-state-group.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; - -import range from "lodash/range"; -// ui -import { Loader } from "@plane/ui"; - -export const IssuesByStateGroupWidgetLoader = () => ( - - -
-
-
- -
-
-
-
- {range(5).map((index) => ( - - ))} -
-
- -); diff --git a/web/core/components/home/widgets/loaders/loader.tsx b/web/core/components/home/widgets/loaders/loader.tsx index ae4038b38d..0edb95346b 100644 --- a/web/core/components/home/widgets/loaders/loader.tsx +++ b/web/core/components/home/widgets/loaders/loader.tsx @@ -1,30 +1,24 @@ // components -import { TWidgetKeys } from "@plane/types"; -import { AssignedIssuesWidgetLoader } from "./assigned-issues"; -import { IssuesByPriorityWidgetLoader } from "./issues-by-priority"; -import { IssuesByStateGroupWidgetLoader } from "./issues-by-state-group"; -import { OverviewStatsWidgetLoader } from "./overview-stats"; import { RecentActivityWidgetLoader } from "./recent-activity"; -import { RecentCollaboratorsWidgetLoader } from "./recent-collaborators"; -import { RecentProjectsWidgetLoader } from "./recent-projects"; +import { QuickLinksWidgetLoader } from "./quick-links"; + // types type Props = { - widgetKey: TWidgetKeys; + widgetKey: EWidgetKeys; }; +export enum EWidgetKeys { + RECENT_ACTIVITY = "recent_activity", + QUICK_LINKS = "quick_links", +} + export const WidgetLoader: React.FC = (props) => { const { widgetKey } = props; const loaders = { - overview_stats: , - assigned_issues: , - created_issues: , - issues_by_state_groups: , - issues_by_priority: , - recent_activity: , - recent_projects: , - recent_collaborators: , + [EWidgetKeys.RECENT_ACTIVITY]: , + [EWidgetKeys.QUICK_LINKS]: , }; return loaders[widgetKey]; diff --git a/web/core/components/home/widgets/loaders/overview-stats.tsx b/web/core/components/home/widgets/loaders/overview-stats.tsx deleted file mode 100644 index e780bb3996..0000000000 --- a/web/core/components/home/widgets/loaders/overview-stats.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import range from "lodash/range"; -// ui -import { Loader } from "@plane/ui"; - -export const OverviewStatsWidgetLoader = () => ( - - {range(4).map((index) => ( -
- - -
- ))} -
-); diff --git a/web/core/components/home/widgets/loaders/quick-links.tsx b/web/core/components/home/widgets/loaders/quick-links.tsx new file mode 100644 index 0000000000..e4b8deafcb --- /dev/null +++ b/web/core/components/home/widgets/loaders/quick-links.tsx @@ -0,0 +1,13 @@ +"use client"; + +import range from "lodash/range"; +// ui +import { Loader } from "@plane/ui"; + +export const QuickLinksWidgetLoader = () => ( + + {range(4).map((index) => ( + + ))} + +); diff --git a/web/core/components/home/widgets/loaders/recent-activity.tsx b/web/core/components/home/widgets/loaders/recent-activity.tsx index 2df78a15af..2f78db64a0 100644 --- a/web/core/components/home/widgets/loaders/recent-activity.tsx +++ b/web/core/components/home/widgets/loaders/recent-activity.tsx @@ -5,16 +5,14 @@ import range from "lodash/range"; import { Loader } from "@plane/ui"; export const RecentActivityWidgetLoader = () => ( - - + {range(7).map((index) => (
- +
-
+
-
))} diff --git a/web/core/components/home/widgets/loaders/recent-collaborators.tsx b/web/core/components/home/widgets/loaders/recent-collaborators.tsx deleted file mode 100644 index 2dceaf1320..0000000000 --- a/web/core/components/home/widgets/loaders/recent-collaborators.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import range from "lodash/range"; -// ui -import { Loader } from "@plane/ui"; - -export const RecentCollaboratorsWidgetLoader = () => ( - <> - {range(8).map((index) => ( - -
-
- -
- -
-
- ))} - -); diff --git a/web/core/components/home/widgets/loaders/recent-projects.tsx b/web/core/components/home/widgets/loaders/recent-projects.tsx deleted file mode 100644 index 38bc7e29a0..0000000000 --- a/web/core/components/home/widgets/loaders/recent-projects.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import range from "lodash/range"; -// ui -import { Loader } from "@plane/ui"; - -export const RecentProjectsWidgetLoader = () => ( - - - {range(5).map((index) => ( -
-
- -
-
- - -
-
- ))} -
-); diff --git a/web/core/components/home/widgets/manage/index.tsx b/web/core/components/home/widgets/manage/index.tsx index 9a2c60c440..670212c6f5 100644 --- a/web/core/components/home/widgets/manage/index.tsx +++ b/web/core/components/home/widgets/manage/index.tsx @@ -4,7 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; // plane types // plane ui -import { Button, ModalCore } from "@plane/ui"; +import { Button, EModalWidth, ModalCore } from "@plane/ui"; import { WidgetList } from "./widget-list"; export type TProps = { @@ -18,7 +18,7 @@ export const ManageWidgetsModal: FC = observer((props) => { const { workspaceSlug, isModalOpen, handleOnClose } = props; return ( - +
Manage widgets
diff --git a/web/core/components/home/widgets/manage/widget-item.tsx b/web/core/components/home/widgets/manage/widget-item.tsx index 2e2dc38029..d9c4523b85 100644 --- a/web/core/components/home/widgets/manage/widget-item.tsx +++ b/web/core/components/home/widgets/manage/widget-item.tsx @@ -112,14 +112,16 @@ export const WidgetItem: FC = observer((props) => {
- -
{widget.title}
+
+ +
{widget.title}
+
{isLastChild && } diff --git a/web/core/components/home/widgets/manage/widget-list.tsx b/web/core/components/home/widgets/manage/widget-list.tsx index 4d93275c99..485f6891b2 100644 --- a/web/core/components/home/widgets/manage/widget-list.tsx +++ b/web/core/components/home/widgets/manage/widget-list.tsx @@ -3,9 +3,9 @@ import { DropTargetRecord, ElementDragPayload, } from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types"; -import { useDashboard } from "@/hooks/store"; import { WidgetItem } from "./widget-item"; import { getInstructionFromPayload, TargetData } from "./widget.helpers"; +import { useHome } from "@/hooks/store/use-home"; const WIDGETS_LIST = [ { id: 1, title: "quick links" }, @@ -13,7 +13,7 @@ const WIDGETS_LIST = [ { id: 3, title: "stickies" }, ]; export const WidgetList = ({ workspaceSlug }: { workspaceSlug: string }) => { - const { reorderWidgets } = useDashboard(); + const { reorderWidgets } = useHome(); const handleDrop = (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => { const dropTargets = location?.current?.dropTargets ?? []; diff --git a/web/core/components/home/widgets/recents/filters.tsx b/web/core/components/home/widgets/recents/filters.tsx index 49c5441d48..61edc80885 100644 --- a/web/core/components/home/widgets/recents/filters.tsx +++ b/web/core/components/home/widgets/recents/filters.tsx @@ -3,30 +3,30 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { ChevronDown } from "lucide-react"; -import { TRecentActivityWidgetFilters } from "@plane/types"; import { CustomMenu } from "@plane/ui"; import { cn } from "@plane/utils"; export type TFiltersDropdown = { className?: string; - activeFilter: TRecentActivityWidgetFilters | undefined; - setActiveFilter: (filter: TRecentActivityWidgetFilters) => void; + activeFilter: string; + setActiveFilter: (filter: string) => void; + filters: { name: string; icon?: React.ReactNode }[]; }; -const filters = ["all", "projects", "pages", "issues"]; export const FiltersDropdown: FC = observer((props) => { - const { className, activeFilter, setActiveFilter } = props; + const { className, activeFilter, setActiveFilter, filters } = props; const DropdownOptions = () => filters?.map((filter) => ( { - setActiveFilter(filter); + setActiveFilter(filter.name); }} > -
{filter}
+ {filter.icon &&
{filter.icon}
} +
{`${filter.name}s`}
)); @@ -37,7 +37,7 @@ export const FiltersDropdown: FC = observer((props) => { placement="bottom-start" customButton={ } diff --git a/web/core/components/home/widgets/recents/index.tsx b/web/core/components/home/widgets/recents/index.tsx index 2ec4fa10e3..9ba0862b31 100644 --- a/web/core/components/home/widgets/recents/index.tsx +++ b/web/core/components/home/widgets/recents/index.tsx @@ -1,59 +1,78 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useRef, useState } from "react"; import { observer } from "mobx-react"; -import Link from "next/link"; +import { WidgetProps } from "@/components/dashboard/widgets"; // types -import { TRecentActivityWidgetResponse } from "@plane/types"; +import { TActivityEntityData } from "@plane/types"; // components -import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets"; -// hooks -import { useDashboard, useUser } from "@/hooks/store"; import { FiltersDropdown } from "./filters"; import { RecentIssue } from "./issue"; +import { WorkspaceService } from "@/plane-web/services"; +import useSWR from "swr"; +import { RecentProject } from "./project"; +import { RecentPage } from "./page"; +import { EWidgetKeys, WidgetLoader } from "../loaders"; +import { Briefcase, FileText } from "lucide-react"; +import { LayersIcon } from "@plane/ui"; +import { EmptyWorkspace } from "../empty-states"; -const WIDGET_KEY = "recent_activity"; +const WIDGET_KEY = EWidgetKeys.RECENT_ACTIVITY; +const workspaceService = new WorkspaceService(); +const filters = [ + { name: "all item" }, + { name: "issue", icon: }, + { name: "page", icon: }, + { name: "project", icon: }, +]; export const RecentActivityWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; + const { workspaceSlug } = props; const ref = useRef(null); - // store hooks - const { data: currentUser } = useUser(); - const { activeFilter, setActiveFilter, fetchWidgetStats, getWidgetStats } = useDashboard(); - // derived values - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const redirectionLink = `/${workspaceSlug}/profile/${currentUser?.id}/activity`; + const [filter, setFilter] = useState(filters[0].name); - useEffect(() => { - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { data: recents, isLoading } = useSWR( + workspaceSlug ? `WORKSPACE_RECENT_ACTIVITY_${workspaceSlug}_${filter}` : null, + workspaceSlug + ? () => + workspaceService.fetchWorkspaceRecents( + workspaceSlug.toString(), + filter === filters[0].name ? undefined : filter + ) + : null, + { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } + ); - if (!widgetStats) return ; - - const resolveRecent = (activity: TRecentActivityWidgetResponse) => { - console.log(); - return ; + const resolveRecent = (activity: TActivityEntityData) => { + switch (activity.entity_name) { + case "page": + return ; + case "project": + return ; + case "issue": + return ; + default: + return <>; + } }; - return ( -
-
- - Recent - + if (!isLoading && recents?.length === 0) return ; - + return ( +
+
+
Recents
+ +
- {widgetStats.length > 0 && ( -
- {widgetStats.map((activity) => ( -
{resolveRecent(activity)}
- ))} -
- )} + {isLoading && } + {!isLoading && + recents?.length > 0 && + recents.map((activity: TActivityEntityData) =>
{resolveRecent(activity)}
)}
); }); diff --git a/web/core/components/home/widgets/recents/issue.tsx b/web/core/components/home/widgets/recents/issue.tsx index 758922c853..14095d6903 100644 --- a/web/core/components/home/widgets/recents/issue.tsx +++ b/web/core/components/home/widgets/recents/issue.tsx @@ -1,23 +1,25 @@ -import { TRecentActivityWidgetResponse } from "@plane/types"; -import { PriorityIcon } from "@plane/ui"; +import { TActivityEntityData, TIssueEntityData } from "@plane/types"; +import { PriorityIcon, StateGroupIcon } from "@plane/ui"; import { ListItem } from "@/components/core/list"; import { calculateTimeAgo } from "@/helpers/date-time.helper"; -import { useIssueDetail } from "@/hooks/store"; import { IssueIdentifier } from "@/plane-web/components/issues"; +import { MemberDropdown } from "@/components/dropdowns"; +import { useIssueDetail, useProjectState } from "@/hooks/store"; type BlockProps = { - activity: TRecentActivityWidgetResponse; + activity: TActivityEntityData; ref: React.RefObject; + workspaceSlug: string; }; export const RecentIssue = (props: BlockProps) => { - const { activity, ref } = props; - const { - issue: { getIssueById }, - } = useIssueDetail(); - console.log({ ...activity.issue_detail, ...activity }, { ...getIssueById(activity?.issue) }); + const { activity, ref, workspaceSlug } = props; + // hooks + const { getStateById } = useProjectState(); + const { setPeekIssue } = useIssueDetail(); + // derived values + const issueDetails: TIssueEntityData = activity.entity_data as TIssueEntityData; + const state = getStateById(issueDetails?.state); - if (!activity?.issue) return <>; - const issueDetails = getIssueById(activity?.issue); return ( { prependTitleElement={
-
- {activity.issue_detail?.name} -
-
{calculateTimeAgo(activity.updated_at)}
+
{issueDetails?.name}
+
{calculateTimeAgo(activity.visited_at)}
} quickActionElement={ -
- -
- {/* 0 ? "transparent-without-text" : "border-without-text"} - buttonClassName={activity.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""} - showTooltip={activity?.assignee_ids?.length === 0} - placeholder="Assignees" - optionsClassName="z-10" - tooltipContent="" - renderByDefault={isMobile} - /> */} -
+
+ + + {issueDetails?.assignees?.length > 0 && ( +
+ {}} + disabled + multiple + buttonVariant={issueDetails?.assignees?.length > 0 ? "transparent-without-text" : "border-without-text"} + buttonClassName={issueDetails?.assignees?.length > 0 ? "hover:bg-transparent px-0" : ""} + showTooltip={issueDetails?.assignees?.length === 0} + placeholder="Assignees" + optionsClassName="z-10" + tooltipContent="" + /> +
+ )}
} parentRef={ref} disableLink={false} - className="bg-transparent my-auto !px-2 border-custom-border-200/40 py-3" + className="bg-transparent my-auto !px-2 border-none py-3" itemClassName="my-auto" + onItemClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + setPeekIssue({ workspaceSlug, projectId: issueDetails?.project_id, issueId: activity.entity_data.id }); + }} /> ); }; diff --git a/web/core/components/home/widgets/recents/page.tsx b/web/core/components/home/widgets/recents/page.tsx new file mode 100644 index 0000000000..f7361f0310 --- /dev/null +++ b/web/core/components/home/widgets/recents/page.tsx @@ -0,0 +1,63 @@ +import { TActivityEntityData, TPageEntityData } from "@plane/types"; +import { Avatar, Logo } from "@plane/ui"; +import { ListItem } from "@/components/core/list"; +import { calculateTimeAgo } from "@/helpers/date-time.helper"; +import { FileText } from "lucide-react"; +import { getFileURL } from "@plane/utils"; +import { useMember } from "@/hooks/store"; +import { useRouter } from "next/navigation"; + +type BlockProps = { + activity: TActivityEntityData; + ref: React.RefObject; + workspaceSlug: string; +}; +export const RecentPage = (props: BlockProps) => { + const { activity, ref, workspaceSlug } = props; + // router + const router = useRouter(); + // hooks + const { getUserDetails } = useMember(); + // derived values + const pageDetails: TPageEntityData = activity.entity_data as TPageEntityData; + const ownerDetails = getUserDetails(pageDetails?.owned_by); + return ( + +
+ <> + {pageDetails?.logo_props?.in_use ? ( + + ) : ( + + )} + +
+
+ {pageDetails?.project_identifier} +
+
{pageDetails?.name}
+
{calculateTimeAgo(activity.visited_at)}
+
+ } + quickActionElement={ +
+ +
+ } + parentRef={ref} + disableLink={false} + className="bg-transparent my-auto !px-2 border-none py-3" + itemClassName="my-auto" + onItemClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + router.push(`/${workspaceSlug}/projects/${pageDetails?.project_id}/pages/${pageDetails.id}`); + }} + /> + ); +}; diff --git a/web/core/components/home/widgets/recents/project.tsx b/web/core/components/home/widgets/recents/project.tsx new file mode 100644 index 0000000000..18037d2701 --- /dev/null +++ b/web/core/components/home/widgets/recents/project.tsx @@ -0,0 +1,71 @@ +import { TActivityEntityData, TProjectEntityData } from "@plane/types"; +import { Logo } from "@plane/ui"; +import { ListItem } from "@/components/core/list"; +import { calculateTimeAgo } from "@/helpers/date-time.helper"; +import { MemberDropdown } from "@/components/dropdowns"; +import { useRouter } from "next/navigation"; + +type BlockProps = { + activity: TActivityEntityData; + ref: React.RefObject; + workspaceSlug: string; +}; +export const RecentProject = (props: BlockProps) => { + const { activity, ref, workspaceSlug } = props; + // router + const router = useRouter(); + // derived values + const projectDetails: TProjectEntityData = activity.entity_data as TProjectEntityData; + + return ( + +
+ +
+
+ {projectDetails?.identifier} +
+
{projectDetails?.name}
+
{calculateTimeAgo(activity.visited_at)}
+
+ } + quickActionElement={ +
+ {projectDetails?.project_members?.length > 0 && ( +
+ {}} + disabled + multiple + buttonVariant={ + projectDetails?.project_members?.length > 0 ? "transparent-without-text" : "border-without-text" + } + buttonClassName={projectDetails?.project_members?.length > 0 ? "hover:bg-transparent px-0" : ""} + showTooltip={projectDetails?.project_members?.length === 0} + placeholder="Assignees" + optionsClassName="z-10" + tooltipContent="" + /> +
+ )} +
+ } + parentRef={ref} + disableLink={false} + className="bg-transparent my-auto !px-2 border-none py-3" + itemClassName="my-auto" + onItemClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + router.push(`/${workspaceSlug}/projects/${projectDetails?.id}/issues`); + }} + /> + ); +}; diff --git a/web/core/hooks/use-workspace-issue-properties.ts b/web/core/hooks/use-workspace-issue-properties.ts index 4cad3a2df3..a3da029599 100644 --- a/web/core/hooks/use-workspace-issue-properties.ts +++ b/web/core/hooks/use-workspace-issue-properties.ts @@ -4,8 +4,6 @@ import { useCycle, useProjectEstimates, useLabel, useModule, useProjectState } f export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => { const { fetchWorkspaceLabels } = useLabel(); - const { fetchWorkspaceStates } = useProjectState(); - const { getWorkspaceEstimates } = useProjectEstimates(); const { fetchWorkspaceModules } = useModule(); @@ -33,13 +31,6 @@ export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | u { revalidateIfStale: false, revalidateOnFocus: false } ); - // fetch workspace states - useSWR( - workspaceSlug ? `WORKSPACE_STATES_${workspaceSlug}` : null, - workspaceSlug ? () => fetchWorkspaceStates(workspaceSlug.toString()) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); - // fetch workspace estimates useSWR( workspaceSlug ? `WORKSPACE_ESTIMATES_${workspaceSlug}` : null, diff --git a/web/core/layouts/auth-layout/workspace-wrapper.tsx b/web/core/layouts/auth-layout/workspace-wrapper.tsx index ab39dd7614..87588008ea 100644 --- a/web/core/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/core/layouts/auth-layout/workspace-wrapper.tsx @@ -14,7 +14,7 @@ import { Button, setToast, TOAST_TYPE, Tooltip } from "@plane/ui"; // components import { LogoSpinner } from "@/components/common"; // hooks -import { useMember, useProject, useUser, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { useMember, useProject, useProjectState, useUser, useUserPermissions, useWorkspace } from "@/hooks/store"; import { useFavorite } from "@/hooks/store/use-favorite"; import { usePlatformOS } from "@/hooks/use-platform-os"; // local @@ -48,6 +48,7 @@ export const WorkspaceAuthWrapper: FC = observer((props) const { isMobile } = usePlatformOS(); const { loader, workspaceInfoBySlug, fetchUserWorkspaceInfo, fetchUserProjectPermissions, allowPermissions } = useUserPermissions(); + const { fetchWorkspaceStates } = useProjectState(); // derived values const canPerformWorkspaceMemberActions = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -93,6 +94,12 @@ export const WorkspaceAuthWrapper: FC = observer((props) : null, { revalidateIfStale: false, revalidateOnFocus: false } ); + // fetch workspace states + useSWR( + workspaceSlug ? `WORKSPACE_STATES_${workspaceSlug}` : null, + workspaceSlug ? () => fetchWorkspaceStates(workspaceSlug.toString()) : null, + { revalidateIfStale: false, revalidateOnFocus: false } + ); // initialize the local database const { isLoading: isDBInitializing } = useSWRImmutable( diff --git a/web/core/services/workspace.service.ts b/web/core/services/workspace.service.ts index 09e27bbdd3..6c3885c488 100644 --- a/web/core/services/workspace.service.ts +++ b/web/core/services/workspace.service.ts @@ -313,6 +313,7 @@ export class WorkspaceService extends APIService { throw error?.response?.data; }); } + async searchEntity(workspaceSlug: string, params: TSearchEntityRequestPayload): Promise { return this.get(`/api/workspaces/${workspaceSlug}/entity-search/`, { params: { @@ -325,4 +326,17 @@ export class WorkspaceService extends APIService { throw error?.response?.data; }); } + + // recents + async fetchWorkspaceRecents(workspaceSlug: string, entity_name?: string) { + return this.get(`/api/workspaces/${workspaceSlug}/recent-visits/`, { + params: { + entity_name, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } } diff --git a/web/core/store/workspace/home.ts b/web/core/store/workspace/home.ts index 607ec37d01..355bdd86a2 100644 --- a/web/core/store/workspace/home.ts +++ b/web/core/store/workspace/home.ts @@ -1,5 +1,6 @@ import { action, makeObservable, observable, runInAction } from "mobx"; import { IWorkspaceLinkStore, WorkspaceLinkStore } from "./link.store"; +import { WorkspaceService } from "@/plane-web/services"; export interface IHomeStore { // observables @@ -18,6 +19,8 @@ export class HomeStore implements IHomeStore { widgetsMap: Record = {}; // stores quickLinks: IWorkspaceLinkStore; + // services + workspaceService: WorkspaceService; constructor() { makeObservable(this, { @@ -29,6 +32,7 @@ export class HomeStore implements IHomeStore { reorderWidgets: action, }); // services + this.workspaceService = new WorkspaceService(); // stores this.quickLinks = new WorkspaceLinkStore(); @@ -69,4 +73,13 @@ export class HomeStore implements IHomeStore { // throw error; // } }; + + // fetchRecentActivity = async (workspaceSlug: string) => { + // try { + // const response = await this.workspaceService.fetchWorkspaceRecents(workspaceSlug); + // } catch (error) { + // console.error("Failed to fetch recent activity"); + // throw error; + // } + // }; }