From f278b1cef99d470773a60b7b7c978d24450a5d6b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 3 Jun 2025 19:18:05 +0530 Subject: [PATCH 1/3] refactor: remove unused dashboard components and fetch keys --- .../dashboard/home-dashboard-widgets.tsx | 62 --- web/core/components/dashboard/index.ts | 3 - .../dashboard/project-empty-state.tsx | 46 -- .../dashboard/widgets/assigned-issues.tsx | 165 ------- .../dashboard/widgets/created-issues.tsx | 162 ------- .../widgets/dropdowns/duration-filter.tsx | 58 --- .../dashboard/widgets/dropdowns/index.ts | 1 - .../widgets/empty-states/assigned-issues.tsx | 55 --- .../widgets/empty-states/created-issues.tsx | 55 --- .../dashboard/widgets/empty-states/index.ts | 6 - .../empty-states/issues-by-priority.tsx | 25 -- .../empty-states/issues-by-state-group.tsx | 25 -- .../widgets/empty-states/recent-activity.tsx | 25 -- .../empty-states/recent-collaborators.tsx | 39 -- .../dashboard/widgets/error-states/index.ts | 1 - .../dashboard/widgets/error-states/issues.tsx | 34 -- .../components/dashboard/widgets/index.ts | 13 - .../dashboard/widgets/issue-panels/index.ts | 3 - .../widgets/issue-panels/issue-list-item.tsx | 401 ------------------ .../widgets/issue-panels/issues-list.tsx | 134 ------ .../widgets/issue-panels/tabs-list.tsx | 61 --- .../dashboard/widgets/issues-by-priority.tsx | 112 ----- .../widgets/issues-by-state-group.tsx | 251 ----------- .../widgets/loaders/assigned-issues.tsx | 24 -- .../dashboard/widgets/loaders/index.ts | 1 - .../widgets/loaders/issues-by-priority.tsx | 17 - .../widgets/loaders/issues-by-state-group.tsx | 24 -- .../dashboard/widgets/loaders/loader.tsx | 31 -- .../widgets/loaders/overview-stats.tsx | 16 - .../widgets/loaders/recent-activity.tsx | 22 - .../widgets/loaders/recent-collaborators.tsx | 20 - .../widgets/loaders/recent-projects.tsx | 22 - .../dashboard/widgets/overview-stats.tsx | 100 ----- .../dashboard/widgets/recent-activity.tsx | 109 ----- .../collaborators-list.tsx | 154 ------- .../widgets/recent-collaborators/index.ts | 1 - .../widgets/recent-collaborators/root.tsx | 36 -- .../dashboard/widgets/recent-projects.tsx | 134 ------ web/core/constants/fetch-keys.ts | 128 +----- web/helpers/string.helper.ts | 29 -- web/package.json | 2 - 41 files changed, 1 insertion(+), 2606 deletions(-) delete mode 100644 web/core/components/dashboard/home-dashboard-widgets.tsx delete mode 100644 web/core/components/dashboard/index.ts delete mode 100644 web/core/components/dashboard/project-empty-state.tsx delete mode 100644 web/core/components/dashboard/widgets/assigned-issues.tsx delete mode 100644 web/core/components/dashboard/widgets/created-issues.tsx delete mode 100644 web/core/components/dashboard/widgets/dropdowns/duration-filter.tsx delete mode 100644 web/core/components/dashboard/widgets/dropdowns/index.ts delete mode 100644 web/core/components/dashboard/widgets/empty-states/assigned-issues.tsx delete mode 100644 web/core/components/dashboard/widgets/empty-states/created-issues.tsx delete mode 100644 web/core/components/dashboard/widgets/empty-states/index.ts delete mode 100644 web/core/components/dashboard/widgets/empty-states/issues-by-priority.tsx delete mode 100644 web/core/components/dashboard/widgets/empty-states/issues-by-state-group.tsx delete mode 100644 web/core/components/dashboard/widgets/empty-states/recent-activity.tsx delete mode 100644 web/core/components/dashboard/widgets/empty-states/recent-collaborators.tsx delete mode 100644 web/core/components/dashboard/widgets/error-states/index.ts delete mode 100644 web/core/components/dashboard/widgets/error-states/issues.tsx delete mode 100644 web/core/components/dashboard/widgets/index.ts delete mode 100644 web/core/components/dashboard/widgets/issue-panels/index.ts delete mode 100644 web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx delete mode 100644 web/core/components/dashboard/widgets/issue-panels/issues-list.tsx delete mode 100644 web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx delete mode 100644 web/core/components/dashboard/widgets/issues-by-priority.tsx delete mode 100644 web/core/components/dashboard/widgets/issues-by-state-group.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/assigned-issues.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/index.ts delete mode 100644 web/core/components/dashboard/widgets/loaders/issues-by-priority.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/loader.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/overview-stats.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/recent-activity.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx delete mode 100644 web/core/components/dashboard/widgets/loaders/recent-projects.tsx delete mode 100644 web/core/components/dashboard/widgets/overview-stats.tsx delete mode 100644 web/core/components/dashboard/widgets/recent-activity.tsx delete mode 100644 web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx delete mode 100644 web/core/components/dashboard/widgets/recent-collaborators/index.ts delete mode 100644 web/core/components/dashboard/widgets/recent-collaborators/root.tsx delete mode 100644 web/core/components/dashboard/widgets/recent-projects.tsx diff --git a/web/core/components/dashboard/home-dashboard-widgets.tsx b/web/core/components/dashboard/home-dashboard-widgets.tsx deleted file mode 100644 index fe64278dc99..00000000000 --- a/web/core/components/dashboard/home-dashboard-widgets.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// types -import { TWidgetKeys } from "@plane/types"; -// components -import { - AssignedIssuesWidget, - CreatedIssuesWidget, - IssuesByPriorityWidget, - IssuesByStateGroupWidget, - OverviewStatsWidget, - RecentActivityWidget, - RecentCollaboratorsWidget, - RecentProjectsWidget, - WidgetProps, -} from "@/components/dashboard"; -// hooks -import { useDashboard } from "@/hooks/store"; - -const WIDGETS_LIST: { - [key in TWidgetKeys]: { component: React.FC; fullWidth: boolean }; -} = { - overview_stats: { component: OverviewStatsWidget, fullWidth: true }, - assigned_issues: { component: AssignedIssuesWidget, fullWidth: false }, - created_issues: { component: CreatedIssuesWidget, fullWidth: false }, - issues_by_state_groups: { component: IssuesByStateGroupWidget, fullWidth: false }, - issues_by_priority: { component: IssuesByPriorityWidget, fullWidth: false }, - recent_activity: { component: RecentActivityWidget, fullWidth: false }, - recent_projects: { component: RecentProjectsWidget, fullWidth: false }, - recent_collaborators: { component: RecentCollaboratorsWidget, fullWidth: true }, -}; - -export const DashboardWidgets = observer(() => { - // router - const { workspaceSlug } = useParams(); - // store hooks - const { homeDashboardId, homeDashboardWidgets } = useDashboard(); - - const doesWidgetExist = (widgetKey: TWidgetKeys) => - Boolean(homeDashboardWidgets?.find((widget) => widget.key === widgetKey)); - - if (!workspaceSlug || !homeDashboardId) return null; - - 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/dashboard/index.ts b/web/core/components/dashboard/index.ts deleted file mode 100644 index 129cdb69ea3..00000000000 --- a/web/core/components/dashboard/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./widgets"; -export * from "./home-dashboard-widgets"; -export * from "./project-empty-state"; diff --git a/web/core/components/dashboard/project-empty-state.tsx b/web/core/components/dashboard/project-empty-state.tsx deleted file mode 100644 index eb06d21be00..00000000000 --- a/web/core/components/dashboard/project-empty-state.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -import Image from "next/image"; -// ui -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; -import { Button } from "@plane/ui"; -// hooks -import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store"; -// assets -import ProjectEmptyStateImage from "@/public/empty-state/onboarding/dashboard-light.webp"; - -export const DashboardProjectEmptyState = observer(() => { - // store hooks - const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); - const { allowPermissions } = useUserPermissions(); - - // derived values - const canCreateProject = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); - - return ( -
-

Overview of your projects, activity, and metrics

-

- Welcome to Plane, we are excited to have you here. Create your first project and track your work items, and this - page will transform into a space that helps you progress. Admins will also see items which help their team - progress. -

- Project empty state - {canCreateProject && ( -
- -
- )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/assigned-issues.tsx b/web/core/components/dashboard/widgets/assigned-issues.tsx deleted file mode 100644 index cc2b74314e1..00000000000 --- a/web/core/components/dashboard/widgets/assigned-issues.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { useEffect, useState } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import { Tab } from "@headlessui/react"; -import { EDurationFilters, FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "@plane/constants"; -import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types"; -// hooks -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesErrorState, - TabsList, - WidgetIssuesList, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { getCustomDates, getRedirectionFilters, getTabKey } from "@/helpers/dashboard.helper"; -import { useDashboard } from "@/hooks/store"; -// components -// helpers -// types -// constants - -const WIDGET_KEY = "assigned_issues"; - -export const AssignedIssuesWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // states - const [fetching, setFetching] = useState(false); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, getWidgetStatsError, updateDashboardWidgetFilters } = - useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStatsError = getWidgetStatsError(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDurationFilter = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedTab = getTabKey(selectedDurationFilter, widgetDetails?.widget_filters.tab); - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - setFetching(true); - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDurationFilter, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - issue_type: filters.tab ?? selectedTab, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - expand: "issue_relation", - }).finally(() => setFetching(false)); - }; - - useEffect(() => { - const filterDates = getCustomDates(selectedDurationFilter, selectedCustomDates); - - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - issue_type: selectedTab, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - expand: "issue_relation", - }); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const filterParams = getRedirectionFilters(selectedTab); - const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; - const selectedTabIndex = tabsList.findIndex((tab) => tab.key === selectedTab); - - if ((!widgetDetails || !widgetStats) && !widgetStatsError) return ; - - return ( - - {widgetStatsError ? ( - - handleUpdateFilters({ - duration: EDurationFilters.NONE, - tab: "pending", - }) - } - /> - ) : ( - widgetStats && ( - <> -
- - Assigned to you - - { - if (val === "custom" && customDates) { - handleUpdateFilters({ - duration: val, - custom_dates: customDates, - }); - return; - } - - if (val === selectedDurationFilter) return; - - let newTab = selectedTab; - // switch to pending tab if target date is changed to none - if (val === "none" && selectedTab !== "completed") newTab = "pending"; - // switch to upcoming tab if target date is changed to other than none - if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") - newTab = "upcoming"; - - handleUpdateFilters({ - duration: val, - tab: newTab, - }); - }} - /> -
- { - const newSelectedTab = tabsList[i]; - handleUpdateFilters({ tab: newSelectedTab?.key ?? "completed" }); - }} - className="h-full flex flex-col" - > - - - {tabsList.map((tab) => { - if (tab.key !== selectedTab) return null; - - return ( - - - - ); - })} - - - - ) - )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/created-issues.tsx b/web/core/components/dashboard/widgets/created-issues.tsx deleted file mode 100644 index 47dc6c268eb..00000000000 --- a/web/core/components/dashboard/widgets/created-issues.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { useEffect, useState } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import { Tab } from "@headlessui/react"; -import { EDurationFilters, FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "@plane/constants"; -import { TCreatedIssuesWidgetFilters, TCreatedIssuesWidgetResponse } from "@plane/types"; -// hooks -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesErrorState, - TabsList, - WidgetIssuesList, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { getCustomDates, getRedirectionFilters, getTabKey } from "@/helpers/dashboard.helper"; -import { useDashboard } from "@/hooks/store"; -// components -// helpers -// types -// constants - -const WIDGET_KEY = "created_issues"; - -export const CreatedIssuesWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // states - const [fetching, setFetching] = useState(false); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, getWidgetStatsError, updateDashboardWidgetFilters } = - useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStatsError = getWidgetStatsError(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDurationFilter = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedTab = getTabKey(selectedDurationFilter, widgetDetails?.widget_filters.tab); - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - setFetching(true); - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDurationFilter, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - issue_type: filters.tab ?? selectedTab, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }).finally(() => setFetching(false)); - }; - - useEffect(() => { - const filterDates = getCustomDates(selectedDurationFilter, selectedCustomDates); - - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - issue_type: selectedTab, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const filterParams = getRedirectionFilters(selectedTab); - const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; - const selectedTabIndex = tabsList.findIndex((tab) => tab.key === selectedTab); - - if ((!widgetDetails || !widgetStats) && !widgetStatsError) return ; - - return ( - - {widgetStatsError ? ( - - handleUpdateFilters({ - duration: EDurationFilters.NONE, - tab: "pending", - }) - } - /> - ) : ( - widgetStats && ( - <> -
- - Created by you - - { - if (val === "custom" && customDates) { - handleUpdateFilters({ - duration: val, - custom_dates: customDates, - }); - return; - } - - if (val === selectedDurationFilter) return; - - let newTab = selectedTab; - // switch to pending tab if target date is changed to none - if (val === "none" && selectedTab !== "completed") newTab = "pending"; - // switch to upcoming tab if target date is changed to other than none - if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") - newTab = "upcoming"; - - handleUpdateFilters({ - duration: val, - tab: newTab, - }); - }} - /> -
- { - const newSelectedTab = tabsList[i]; - handleUpdateFilters({ tab: newSelectedTab.key ?? "completed" }); - }} - className="h-full flex flex-col" - > - - - {tabsList.map((tab) => { - if (tab.key !== selectedTab) return null; - - return ( - - - - ); - })} - - - - ) - )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/dropdowns/duration-filter.tsx b/web/core/components/dashboard/widgets/dropdowns/duration-filter.tsx deleted file mode 100644 index 2a897de82a9..00000000000 --- a/web/core/components/dashboard/widgets/dropdowns/duration-filter.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { ChevronDown } from "lucide-react"; -// components -import { DURATION_FILTER_OPTIONS, EDurationFilters } from "@plane/constants"; -import { CustomMenu } from "@plane/ui"; -import { DateFilterModal } from "@/components/core"; -// ui -// helpers -import { getDurationFilterDropdownLabel } from "@/helpers/dashboard.helper"; -// constants - -type Props = { - customDates?: string[]; - onChange: (value: EDurationFilters, customDates?: string[]) => void; - value: EDurationFilters; -}; - -export const DurationFilterDropdown: React.FC = (props) => { - const { customDates, onChange, value } = props; - // states - const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); - - return ( - <> - setIsDateFilterModalOpen(false)} - onSelect={(val) => onChange(EDurationFilters.CUSTOM, val)} - title="Due date" - /> - - {getDurationFilterDropdownLabel(value, customDates ?? [])} - - - } - placement="bottom-end" - closeOnSelect - > - {DURATION_FILTER_OPTIONS.map((option) => ( - { - if (option.key === "custom") setIsDateFilterModalOpen(true); - else onChange(option.key); - }} - > - {option.label} - - ))} - - - ); -}; diff --git a/web/core/components/dashboard/widgets/dropdowns/index.ts b/web/core/components/dashboard/widgets/dropdowns/index.ts deleted file mode 100644 index cff4cdb449c..00000000000 --- a/web/core/components/dashboard/widgets/dropdowns/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./duration-filter"; diff --git a/web/core/components/dashboard/widgets/empty-states/assigned-issues.tsx b/web/core/components/dashboard/widgets/empty-states/assigned-issues.tsx deleted file mode 100644 index 169547846e4..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/assigned-issues.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -import { TIssuesListTypes } from "@plane/types"; -import CompletedIssuesDark from "@/public/empty-state/dashboard/dark/completed-issues.svg"; -import OverdueIssuesDark from "@/public/empty-state/dashboard/dark/overdue-issues.svg"; -import UpcomingIssuesDark from "@/public/empty-state/dashboard/dark/upcoming-issues.svg"; -import CompletedIssuesLight from "@/public/empty-state/dashboard/light/completed-issues.svg"; -import OverdueIssuesLight from "@/public/empty-state/dashboard/light/overdue-issues.svg"; -import UpcomingIssuesLight from "@/public/empty-state/dashboard/light/upcoming-issues.svg"; - -export const ASSIGNED_ISSUES_EMPTY_STATES = { - pending: { - title: "Work items assigned to you that are pending\nwill show up here.", - darkImage: UpcomingIssuesDark, - lightImage: UpcomingIssuesLight, - }, - upcoming: { - title: "Upcoming work items assigned to\nyou will show up here.", - darkImage: UpcomingIssuesDark, - lightImage: UpcomingIssuesLight, - }, - overdue: { - title: "Work items assigned to you that are past\ntheir due date will show up here.", - darkImage: OverdueIssuesDark, - lightImage: OverdueIssuesLight, - }, - completed: { - title: "Work items assigned to you that you have\nmarked Completed will show up here.", - darkImage: CompletedIssuesDark, - lightImage: CompletedIssuesLight, - }, -}; -type Props = { - type: TIssuesListTypes; -}; - -export const AssignedIssuesEmptyState: React.FC = (props) => { - const { type } = props; - // next-themes - const { resolvedTheme } = useTheme(); - - const typeDetails = ASSIGNED_ISSUES_EMPTY_STATES[type]; - - const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage; - - // TODO: update empty state logic to use a general component - return ( -
-
- Assigned work items -
-

{typeDetails.title}

-
- ); -}; diff --git a/web/core/components/dashboard/widgets/empty-states/created-issues.tsx b/web/core/components/dashboard/widgets/empty-states/created-issues.tsx deleted file mode 100644 index 49a96f1fb3b..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/created-issues.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -import { TIssuesListTypes } from "@plane/types"; -import CompletedIssuesDark from "@/public/empty-state/dashboard/dark/completed-issues.svg"; -import OverdueIssuesDark from "@/public/empty-state/dashboard/dark/overdue-issues.svg"; -import UpcomingIssuesDark from "@/public/empty-state/dashboard/dark/upcoming-issues.svg"; -import CompletedIssuesLight from "@/public/empty-state/dashboard/light/completed-issues.svg"; -import OverdueIssuesLight from "@/public/empty-state/dashboard/light/overdue-issues.svg"; -import UpcomingIssuesLight from "@/public/empty-state/dashboard/light/upcoming-issues.svg"; - -export const CREATED_ISSUES_EMPTY_STATES = { - pending: { - title: "Work items created by you that are pending\nwill show up here.", - darkImage: UpcomingIssuesDark, - lightImage: UpcomingIssuesLight, - }, - upcoming: { - title: "Upcoming work items you created\nwill show up here.", - darkImage: UpcomingIssuesDark, - lightImage: UpcomingIssuesLight, - }, - overdue: { - title: "Work items created by you that are past their\ndue date will show up here.", - darkImage: OverdueIssuesDark, - lightImage: OverdueIssuesLight, - }, - completed: { - title: "Work items created by you that you have\nmarked completed will show up here.", - darkImage: CompletedIssuesDark, - lightImage: CompletedIssuesLight, - }, -}; - -type Props = { - type: TIssuesListTypes; -}; - -export const CreatedIssuesEmptyState: React.FC = (props) => { - const { type } = props; - // next-themes - const { resolvedTheme } = useTheme(); - - const typeDetails = CREATED_ISSUES_EMPTY_STATES[type]; - - const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage; - - return ( -
-
- Assigned work items -
-

{typeDetails.title}

-
- ); -}; diff --git a/web/core/components/dashboard/widgets/empty-states/index.ts b/web/core/components/dashboard/widgets/empty-states/index.ts deleted file mode 100644 index 72ca1dbb2dc..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./assigned-issues"; -export * from "./created-issues"; -export * from "./issues-by-priority"; -export * from "./issues-by-state-group"; -export * from "./recent-activity"; -export * from "./recent-collaborators"; diff --git a/web/core/components/dashboard/widgets/empty-states/issues-by-priority.tsx b/web/core/components/dashboard/widgets/empty-states/issues-by-priority.tsx deleted file mode 100644 index 7f70d3bf303..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/issues-by-priority.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -// assets -import DarkImage from "@/public/empty-state/dashboard/dark/issues-by-priority.svg"; -import LightImage from "@/public/empty-state/dashboard/light/issues-by-priority.svg"; - -export const IssuesByPriorityEmptyState = () => { - // next-themes - const { resolvedTheme } = useTheme(); - - const image = resolvedTheme === "dark" ? DarkImage : LightImage; - - return ( -
-
- Work items by state group -
-

- Work items assigned to you, broken down by -
- priority will show up here. -

-
- ); -}; diff --git a/web/core/components/dashboard/widgets/empty-states/issues-by-state-group.tsx b/web/core/components/dashboard/widgets/empty-states/issues-by-state-group.tsx deleted file mode 100644 index 69d4761e5ec..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/issues-by-state-group.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -// assets -import DarkImage from "@/public/empty-state/dashboard/dark/issues-by-state-group.svg"; -import LightImage from "@/public/empty-state/dashboard/light/issues-by-state-group.svg"; - -export const IssuesByStateGroupEmptyState = () => { - // next-themes - const { resolvedTheme } = useTheme(); - - const image = resolvedTheme === "dark" ? DarkImage : LightImage; - - return ( -
-
- Work items by state group -
-

- Work items assigned to you, broken down by state, -
- will show up here. -

-
- ); -}; diff --git a/web/core/components/dashboard/widgets/empty-states/recent-activity.tsx b/web/core/components/dashboard/widgets/empty-states/recent-activity.tsx deleted file mode 100644 index f25f3992088..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/recent-activity.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -// assets -import DarkImage from "@/public/empty-state/dashboard/dark/recent-activity.svg"; -import LightImage from "@/public/empty-state/dashboard/light/recent-activity.svg"; - -export const RecentActivityEmptyState = () => { - // next-themes - const { resolvedTheme } = useTheme(); - - const image = resolvedTheme === "dark" ? DarkImage : LightImage; - - return ( -
-
- Work items by state group -
-

- All your work items activities across -
- projects will show up here. -

-
- ); -}; diff --git a/web/core/components/dashboard/widgets/empty-states/recent-collaborators.tsx b/web/core/components/dashboard/widgets/empty-states/recent-collaborators.tsx deleted file mode 100644 index d1a1200aa59..00000000000 --- a/web/core/components/dashboard/widgets/empty-states/recent-collaborators.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import Image from "next/image"; -import { useTheme } from "next-themes"; -// assets -import DarkImage1 from "@/public/empty-state/dashboard/dark/recent-collaborators-1.svg"; -import DarkImage2 from "@/public/empty-state/dashboard/dark/recent-collaborators-2.svg"; -import DarkImage3 from "@/public/empty-state/dashboard/dark/recent-collaborators-3.svg"; -import LightImage1 from "@/public/empty-state/dashboard/light/recent-collaborators-1.svg"; -import LightImage2 from "@/public/empty-state/dashboard/light/recent-collaborators-2.svg"; -import LightImage3 from "@/public/empty-state/dashboard/light/recent-collaborators-3.svg"; - -export const RecentCollaboratorsEmptyState = () => { - // next-themes - const { resolvedTheme } = useTheme(); - - const image1 = resolvedTheme === "dark" ? DarkImage1 : LightImage1; - const image2 = resolvedTheme === "dark" ? DarkImage2 : LightImage2; - const image3 = resolvedTheme === "dark" ? DarkImage3 : LightImage3; - - return ( -
-

- Compare your activities with the top -
- seven in your project. -

-
-
- Recent collaborators -
-
- Recent collaborators -
-
- Recent collaborators -
-
-
- ); -}; diff --git a/web/core/components/dashboard/widgets/error-states/index.ts b/web/core/components/dashboard/widgets/error-states/index.ts deleted file mode 100644 index bd8854f3744..00000000000 --- a/web/core/components/dashboard/widgets/error-states/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issues"; diff --git a/web/core/components/dashboard/widgets/error-states/issues.tsx b/web/core/components/dashboard/widgets/error-states/issues.tsx deleted file mode 100644 index fb3e3550b44..00000000000 --- a/web/core/components/dashboard/widgets/error-states/issues.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import { AlertTriangle, RefreshCcw } from "lucide-react"; -// ui -import { Button } from "@plane/ui"; - -type Props = { - isRefreshing: boolean; - onClick: () => void; -}; - -export const IssuesErrorState: React.FC = (props) => { - const { isRefreshing, onClick } = props; - - return ( -
-
-
- -
-

There was an error in fetching widget details

- -
-
- ); -}; diff --git a/web/core/components/dashboard/widgets/index.ts b/web/core/components/dashboard/widgets/index.ts deleted file mode 100644 index 31fc645d410..00000000000 --- a/web/core/components/dashboard/widgets/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from "./dropdowns"; -export * from "./empty-states"; -export * from "./error-states"; -export * from "./issue-panels"; -export * from "./loaders"; -export * from "./assigned-issues"; -export * from "./created-issues"; -export * from "./issues-by-priority"; -export * from "./issues-by-state-group"; -export * from "./overview-stats"; -export * from "./recent-activity"; -export * from "./recent-collaborators"; -export * from "./recent-projects"; diff --git a/web/core/components/dashboard/widgets/issue-panels/index.ts b/web/core/components/dashboard/widgets/issue-panels/index.ts deleted file mode 100644 index f5b7d53d49e..00000000000 --- a/web/core/components/dashboard/widgets/issue-panels/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./issue-list-item"; -export * from "./issues-list"; -export * from "./tabs-list"; diff --git a/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx b/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx deleted file mode 100644 index 75471c6fd60..00000000000 --- a/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx +++ /dev/null @@ -1,401 +0,0 @@ -"use client"; - -import { isToday } from "date-fns/isToday"; -import { observer } from "mobx-react"; -// types -import { TIssue, TWidgetIssue } from "@plane/types"; -// ui -import { Avatar, AvatarGroup, ControlLink, PriorityIcon } from "@plane/ui"; -// helpers -import { findTotalDaysInRange, getDate, renderFormattedDate } from "@/helpers/date-time.helper"; -import { getFileURL } from "@/helpers/file.helper"; -import { generateWorkItemLink } from "@/helpers/issue.helper"; -// hooks -import { useIssueDetail, useMember, useProject } from "@/hooks/store"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; - -export type IssueListItemProps = { - issueId: string; - onClick: (issue: TIssue) => void; - workspaceSlug: string; -}; - -export const AssignedUpcomingIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { getProjectById } = useProject(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - // derived values - const issueDetails = getIssueById(issueId) as TWidgetIssue | undefined; - - if (!issueDetails || !issueDetails.project_id) return null; - - const projectDetails = getProjectById(issueDetails.project_id); - - const blockedByIssues = issueDetails.issue_relation?.filter((issue) => issue.relation_type === "blocked_by") ?? []; - - const blockedByIssueProjectDetails = - blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null; - - const targetDate = getDate(issueDetails.target_date); - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issueDetails?.project_id, - issueId: issueDetails?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issueDetails?.sequence_id, - }); - - return ( - onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issueDetails.name}
-
-
- -
-
- {targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"} -
-
- {blockedByIssues.length > 0 - ? blockedByIssues.length > 1 - ? `${blockedByIssues.length} blockers` - : blockedByIssueProjectDetails && ( - - ) - : "-"} -
-
- ); -}); - -export const AssignedOverdueIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { getProjectById } = useProject(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - // derived values - const issueDetails = getIssueById(issueId) as TWidgetIssue | undefined; - - if (!issueDetails || !issueDetails.project_id) return null; - - const projectDetails = getProjectById(issueDetails.project_id); - const blockedByIssues = issueDetails.issue_relation?.filter((issue) => issue.relation_type === "blocked_by") ?? []; - - const blockedByIssueProjectDetails = - blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null; - - const dueBy = findTotalDaysInRange(getDate(issueDetails.target_date), new Date(), false) ?? 0; - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issueDetails?.project_id, - issueId: issueDetails?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issueDetails?.sequence_id, - }); - - return ( - onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issueDetails.name}
-
-
- -
-
- {dueBy} {`day${dueBy > 1 ? "s" : ""}`} -
-
- {blockedByIssues.length > 0 - ? blockedByIssues.length > 1 - ? `${blockedByIssues.length} blockers` - : blockedByIssueProjectDetails && ( - - ) - : "-"} -
-
- ); -}); - -export const AssignedCompletedIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { - issue: { getIssueById }, - } = useIssueDetail(); - const { getProjectById } = useProject(); - // derived values - const issueDetails = getIssueById(issueId); - - if (!issueDetails || !issueDetails.project_id) return null; - - const projectDetails = getProjectById(issueDetails.project_id); - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issueDetails?.project_id, - issueId: issueDetails?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issueDetails?.sequence_id, - }); - - return ( - onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issueDetails.name}
-
-
- -
-
- ); -}); - -export const CreatedUpcomingIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { getUserDetails } = useMember(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - const { getProjectById } = useProject(); - // derived values - const issue = getIssueById(issueId); - - if (!issue || !issue.project_id) return null; - - const projectDetails = getProjectById(issue.project_id); - const targetDate = getDate(issue.target_date); - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issue?.project_id, - issueId: issue?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issue?.sequence_id, - }); - - return ( - onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issue.name}
-
-
- -
-
- {targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"} -
-
- {issue.assignee_ids && issue.assignee_ids?.length > 0 ? ( - - {issue.assignee_ids?.map((assigneeId) => { - const userDetails = getUserDetails(assigneeId); - - if (!userDetails) return null; - - return ( - - ); - })} - - ) : ( - "-" - )} -
-
- ); -}); - -export const CreatedOverdueIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { getUserDetails } = useMember(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - const { getProjectById } = useProject(); - // derived values - const issue = getIssueById(issueId); - - if (!issue || !issue.project_id) return null; - - const projectDetails = getProjectById(issue.project_id); - - const dueBy: number = findTotalDaysInRange(getDate(issue.target_date), new Date(), false) ?? 0; - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issue?.project_id, - issueId: issue?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issue?.sequence_id, - }); - - return ( - onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issue.name}
-
-
- -
-
- {dueBy} {`day${dueBy > 1 ? "s" : ""}`} -
-
- {issue.assignee_ids.length > 0 ? ( - - {issue.assignee_ids?.map((assigneeId) => { - const userDetails = getUserDetails(assigneeId); - - if (!userDetails) return null; - - return ( - - ); - })} - - ) : ( - "-" - )} -
-
- ); -}); - -export const CreatedCompletedIssueListItem: React.FC = observer((props) => { - const { issueId, onClick, workspaceSlug } = props; - // store hooks - const { getUserDetails } = useMember(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - const { getProjectById } = useProject(); - // derived values - const issue = getIssueById(issueId); - - if (!issue || !issue.project_id) return null; - - const projectDetails = getProjectById(issue.project_id); - - const workItemLink = generateWorkItemLink({ - workspaceSlug, - projectId: issue?.project_id, - issueId: issue?.id, - projectIdentifier: projectDetails?.identifier, - sequenceId: issue?.sequence_id, - }); - - return ( - onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" - > -
- {projectDetails && ( - - )} -
{issue.name}
-
-
- -
-
- {issue.assignee_ids.length > 0 ? ( - - {issue.assignee_ids?.map((assigneeId) => { - const userDetails = getUserDetails(assigneeId); - - if (!userDetails) return null; - - return ( - - ); - })} - - ) : ( - "-" - )} -
-
- ); -}); diff --git a/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx b/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx deleted file mode 100644 index 925e2ee9af8..00000000000 --- a/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { TAssignedIssuesWidgetResponse, TCreatedIssuesWidgetResponse, TIssue, TIssuesListTypes } from "@plane/types"; -// hooks -// components -import { Loader, getButtonStyling } from "@plane/ui"; -import { - AssignedCompletedIssueListItem, - AssignedIssuesEmptyState, - AssignedOverdueIssueListItem, - AssignedUpcomingIssueListItem, - CreatedCompletedIssueListItem, - CreatedIssuesEmptyState, - CreatedOverdueIssueListItem, - CreatedUpcomingIssueListItem, - IssueListItemProps, -} from "@/components/dashboard/widgets"; -// ui -// helpers -import { cn } from "@/helpers/common.helper"; -import { getRedirectionFilters } from "@/helpers/dashboard.helper"; -// hooks -import useIssuePeekOverviewRedirection from "@/hooks/use-issue-peek-overview-redirection"; -import { usePlatformOS } from "@/hooks/use-platform-os"; - -export type WidgetIssuesListProps = { - isLoading: boolean; - tab: TIssuesListTypes; - type: "assigned" | "created"; - widgetStats: TAssignedIssuesWidgetResponse | TCreatedIssuesWidgetResponse; - workspaceSlug: string; -}; - -export const WidgetIssuesList: React.FC = (props) => { - const { isLoading, tab, type, widgetStats, workspaceSlug } = props; - // hooks - const { isMobile } = usePlatformOS(); - const { handleRedirection } = useIssuePeekOverviewRedirection(); - - // handlers - const handleIssuePeekOverview = (issue: TIssue) => handleRedirection(workspaceSlug, issue, isMobile); - - const filterParams = getRedirectionFilters(tab); - - const ISSUE_LIST_ITEM: { - [key: string]: { - [key in TIssuesListTypes]: React.FC; - }; - } = { - assigned: { - pending: AssignedUpcomingIssueListItem, - upcoming: AssignedUpcomingIssueListItem, - overdue: AssignedOverdueIssueListItem, - completed: AssignedCompletedIssueListItem, - }, - created: { - pending: CreatedUpcomingIssueListItem, - upcoming: CreatedUpcomingIssueListItem, - overdue: CreatedOverdueIssueListItem, - completed: CreatedCompletedIssueListItem, - }, - }; - - const issuesList = widgetStats.issues; - - return ( - <> -
- {isLoading ? ( - - - - - - - ) : issuesList.length > 0 ? ( - <> -
-
- Work items - - {widgetStats.count} - -
-
Priority
- {["upcoming", "pending"].includes(tab) &&
Due date
} - {tab === "overdue" &&
Due by
} - {type === "assigned" && tab !== "completed" &&
Blocked by
} - {type === "created" &&
Assigned to
} -
-
- {issuesList.map((issue) => { - const IssueListItem = ISSUE_LIST_ITEM[type][tab]; - - if (!IssueListItem) return null; - - return ( - - ); - })} -
- - ) : ( -
- {type === "assigned" && } - {type === "created" && } -
- )} -
- {!isLoading && issuesList.length > 0 && ( - - View all work items - - )} - - ); -}; diff --git a/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx b/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx deleted file mode 100644 index 9df044c37b7..00000000000 --- a/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { observer } from "mobx-react"; -import { Tab } from "@headlessui/react"; -// helpers -import { EDurationFilters, FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "@plane/constants"; -import { TIssuesListTypes } from "@plane/types"; -import { cn } from "@/helpers/common.helper"; -// types -// constants - -type Props = { - durationFilter: EDurationFilters; - selectedTab: TIssuesListTypes; -}; - -export const TabsList: React.FC = observer((props) => { - const { durationFilter, selectedTab } = props; - - const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; - const selectedTabIndex = tabsList.findIndex((tab) => tab.key === selectedTab); - - return ( - -
- {tabsList.map((tab) => ( - - {tab.label} - - ))} - - ); -}); diff --git a/web/core/components/dashboard/widgets/issues-by-priority.tsx b/web/core/components/dashboard/widgets/issues-by-priority.tsx deleted file mode 100644 index ac678812057..00000000000 --- a/web/core/components/dashboard/widgets/issues-by-priority.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -// types -import { EDurationFilters } from "@plane/constants"; -import { TIssuesByPriorityWidgetFilters, TIssuesByPriorityWidgetResponse } from "@plane/types"; -// components -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesByPriorityEmptyState, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { IssuesByPriorityGraph } from "@/components/graphs"; -// constants -// helpers -import { getCustomDates } from "@/helpers/dashboard.helper"; -// hooks -import { useDashboard } from "@/hooks/store"; -import { useAppRouter } from "@/hooks/use-app-router"; - -const WIDGET_KEY = "issues_by_priority"; - -export const IssuesByPriorityWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // router - const router = useAppRouter(); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDuration = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDuration, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - }; - - useEffect(() => { - const filterDates = getCustomDates(selectedDuration, selectedCustomDates); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!widgetDetails || !widgetStats) return ; - - const totalCount = widgetStats.reduce((acc, item) => acc + item?.count, 0); - const chartData = widgetStats.map((item) => ({ - priority: item?.priority, - priority_count: item?.count, - })); - - return ( - -
- - Assigned by priority - - - handleUpdateFilters({ - duration: val, - ...(val === "custom" ? { custom_dates: customDates } : {}), - }) - } - /> -
- {totalCount > 0 ? ( -
-
- { - router.push( - `/${workspaceSlug}/workspace-views/assigned?priority=${`${datum.data.priority}`.toLowerCase()}` - ); - }} - /> -
-
- ) : ( -
- -
- )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/issues-by-state-group.tsx b/web/core/components/dashboard/widgets/issues-by-state-group.tsx deleted file mode 100644 index fec4204cfc3..00000000000 --- a/web/core/components/dashboard/widgets/issues-by-state-group.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { useEffect, useState } from "react"; -import { linearGradientDef } from "@nivo/core"; -import { observer } from "mobx-react"; -import Link from "next/link"; -// types -import { EDurationFilters, STATE_GROUPS } from "@plane/constants"; -import { TIssuesByStateGroupsWidgetFilters, TIssuesByStateGroupsWidgetResponse, TStateGroups } from "@plane/types"; -// components -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesByStateGroupEmptyState, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { PieGraph } from "@/components/ui"; -// helpers -import { getCustomDates } from "@/helpers/dashboard.helper"; -// hooks -import { useDashboard } from "@/hooks/store"; -import { useAppRouter } from "@/hooks/use-app-router"; - -const WIDGET_KEY = "issues_by_state_groups"; - -export const STATE_GROUP_GRAPH_COLORS: Record = { - backlog: "#CDCED6", - unstarted: "#80838D", - started: "#FFC53D", - completed: "#3E9B4F", - cancelled: "#E5484D", -}; -// colors for work items by state group widget graph arcs -export const STATE_GROUP_GRAPH_GRADIENTS = [ - linearGradientDef("gradientBacklog", [ - { offset: 0, color: "#DEDEDE" }, - { offset: 100, color: "#BABABE" }, - ]), - linearGradientDef("gradientUnstarted", [ - { offset: 0, color: "#D4D4D4" }, - { offset: 100, color: "#878796" }, - ]), - linearGradientDef("gradientStarted", [ - { offset: 0, color: "#FFD300" }, - { offset: 100, color: "#FAE270" }, - ]), - linearGradientDef("gradientCompleted", [ - { offset: 0, color: "#0E8B1B" }, - { offset: 100, color: "#37CB46" }, - ]), - linearGradientDef("gradientCanceled", [ - { offset: 0, color: "#C90004" }, - { offset: 100, color: "#FF7679" }, - ]), -]; - -export const IssuesByStateGroupWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // states - const [defaultStateGroup, setDefaultStateGroup] = useState(null); - const [activeStateGroup, setActiveStateGroup] = useState(null); - // router - const router = useAppRouter(); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDuration = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDuration, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - }; - - // fetch widget stats - useEffect(() => { - const filterDates = getCustomDates(selectedDuration, selectedCustomDates); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // set active group for center metric - useEffect(() => { - if (!widgetStats) return; - - const startedCount = widgetStats?.find((item) => item?.state === "started")?.count ?? 0; - const unStartedCount = widgetStats?.find((item) => item?.state === "unstarted")?.count ?? 0; - const backlogCount = widgetStats?.find((item) => item?.state === "backlog")?.count ?? 0; - const completedCount = widgetStats?.find((item) => item?.state === "completed")?.count ?? 0; - const canceledCount = widgetStats?.find((item) => item?.state === "cancelled")?.count ?? 0; - - const stateGroup = - startedCount > 0 - ? "started" - : unStartedCount > 0 - ? "unstarted" - : backlogCount > 0 - ? "backlog" - : completedCount > 0 - ? "completed" - : canceledCount > 0 - ? "cancelled" - : null; - - setActiveStateGroup(stateGroup); - setDefaultStateGroup(stateGroup); - }, [widgetStats]); - - if (!widgetDetails || !widgetStats) return ; - - const totalCount = widgetStats?.reduce((acc, item) => acc + item?.count, 0); - const chartData = widgetStats?.map((item) => ({ - color: STATE_GROUP_GRAPH_COLORS[item?.state as keyof typeof STATE_GROUP_GRAPH_COLORS], - id: item?.state, - label: item?.state, - value: (item?.count / totalCount) * 100, - })); - - const CenteredMetric = ({ dataWithArc, centerX, centerY }: any) => { - const data = dataWithArc?.find((datum: any) => datum?.id === activeStateGroup); - const percentage = chartData?.find((item) => item.id === activeStateGroup)?.value?.toFixed(0); - - return ( - - - {percentage}% - - - {data?.id} - - - ); - }; - - return ( - -
- - Assigned by state - - - handleUpdateFilters({ - duration: val, - ...(val === "custom" ? { custom_dates: customDates } : {}), - }) - } - /> -
- {totalCount > 0 ? ( -
-
-
- datum.data.color} - padAngle={1} - enableArcLinkLabels={false} - enableArcLabels={false} - activeOuterRadiusOffset={5} - tooltip={() => <>} - margin={{ - top: 0, - right: 5, - bottom: 0, - left: 5, - }} - defs={STATE_GROUP_GRAPH_GRADIENTS} - fill={Object.values(STATE_GROUPS).map((p) => ({ - match: { - id: p.key, - }, - id: `gradient${p.label}`, - }))} - onClick={(datum, e) => { - e.preventDefault(); - e.stopPropagation(); - router.push(`/${workspaceSlug}/workspace-views/assigned/?state_group=${datum.id}`); - }} - onMouseEnter={(datum) => setActiveStateGroup(datum.id as TStateGroups)} - onMouseLeave={() => setActiveStateGroup(defaultStateGroup)} - layers={["arcs", CenteredMetric]} - /> -
-
- {chartData.map((item) => ( -
-
-
- {item.label} -
- {item.value.toFixed(0)}% -
- ))} -
-
-
- ) : ( -
- -
- )} - - ); -}); diff --git a/web/core/components/dashboard/widgets/loaders/assigned-issues.tsx b/web/core/components/dashboard/widgets/loaders/assigned-issues.tsx deleted file mode 100644 index 8a78fedf1a4..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/loaders/index.ts b/web/core/components/dashboard/widgets/loaders/index.ts deleted file mode 100644 index ee5286f0fbf..00000000000 --- a/web/core/components/dashboard/widgets/loaders/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./loader"; diff --git a/web/core/components/dashboard/widgets/loaders/issues-by-priority.tsx b/web/core/components/dashboard/widgets/loaders/issues-by-priority.tsx deleted file mode 100644 index c6f075b5806..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/loaders/issues-by-state-group.tsx b/web/core/components/dashboard/widgets/loaders/issues-by-state-group.tsx deleted file mode 100644 index 099323cce75..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/loaders/loader.tsx b/web/core/components/dashboard/widgets/loaders/loader.tsx deleted file mode 100644 index ae4038b38da..00000000000 --- a/web/core/components/dashboard/widgets/loaders/loader.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// 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"; -// types - -type Props = { - widgetKey: TWidgetKeys; -}; - -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: , - }; - - return loaders[widgetKey]; -}; diff --git a/web/core/components/dashboard/widgets/loaders/overview-stats.tsx b/web/core/components/dashboard/widgets/loaders/overview-stats.tsx deleted file mode 100644 index e780bb39966..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/loaders/recent-activity.tsx b/web/core/components/dashboard/widgets/loaders/recent-activity.tsx deleted file mode 100644 index 2df78a15af1..00000000000 --- a/web/core/components/dashboard/widgets/loaders/recent-activity.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import range from "lodash/range"; -// ui -import { Loader } from "@plane/ui"; - -export const RecentActivityWidgetLoader = () => ( - - - {range(7).map((index) => ( -
-
- -
-
- - -
-
- ))} -
-); diff --git a/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx b/web/core/components/dashboard/widgets/loaders/recent-collaborators.tsx deleted file mode 100644 index 2dceaf1320a..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/loaders/recent-projects.tsx b/web/core/components/dashboard/widgets/loaders/recent-projects.tsx deleted file mode 100644 index 38bc7e29a01..00000000000 --- a/web/core/components/dashboard/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/dashboard/widgets/overview-stats.tsx b/web/core/components/dashboard/widgets/overview-stats.tsx deleted file mode 100644 index 20421f33b49..00000000000 --- a/web/core/components/dashboard/widgets/overview-stats.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import { TOverviewStatsWidgetResponse } from "@plane/types"; -// hooks -import { Card, ECardSpacing } from "@plane/ui"; -import { WidgetLoader } from "@/components/dashboard/widgets"; -import { cn } from "@/helpers/common.helper"; -import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; -import { useDashboard } from "@/hooks/store"; -// components -// helpers -// types - -export type WidgetProps = { - dashboardId: string; - workspaceSlug: string; -}; - -const WIDGET_KEY = "overview_stats"; - -export const OverviewStatsWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // store hooks - const { fetchWidgetStats, getWidgetStats } = useDashboard(); - // derived values - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - - const today = renderFormattedPayloadDate(new Date()); - const STATS_LIST = [ - { - key: "assigned", - title: "Work items assigned", - count: widgetStats?.assigned_issues_count, - link: `/${workspaceSlug}/workspace-views/assigned`, - }, - { - key: "overdue", - title: "Work items overdue", - count: widgetStats?.pending_issues_count, - link: `/${workspaceSlug}/workspace-views/assigned/?state_group=backlog,unstarted,started&target_date=${today};before`, - }, - { - key: "created", - title: "Work items created", - count: widgetStats?.created_issues_count, - link: `/${workspaceSlug}/workspace-views/created`, - }, - { - key: "completed", - title: "Work items completed", - count: widgetStats?.completed_issues_count, - link: `/${workspaceSlug}/workspace-views/assigned?state_group=completed`, - }, - ]; - - useEffect(() => { - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!widgetStats) return ; - - return ( - - {STATS_LIST.map((stat, index) => ( -
- -
-
-
{stat.count}
-

{stat.title}

-
-
- -
- ))} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/recent-activity.tsx b/web/core/components/dashboard/widgets/recent-activity.tsx deleted file mode 100644 index 84ac985e10c..00000000000 --- a/web/core/components/dashboard/widgets/recent-activity.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import { History } from "lucide-react"; -// types -import { TRecentActivityWidgetResponse } from "@plane/types"; -// components -import { Card, Avatar, getButtonStyling } from "@plane/ui"; -import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core"; -import { RecentActivityEmptyState, WidgetLoader, WidgetProps } from "@/components/dashboard/widgets"; -// helpers -import { cn } from "@/helpers/common.helper"; -import { calculateTimeAgo } from "@/helpers/date-time.helper"; -import { getFileURL } from "@/helpers/file.helper"; -// hooks -import { useDashboard, useUser } from "@/hooks/store"; - -const WIDGET_KEY = "recent_activity"; - -export const RecentActivityWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // store hooks - const { data: currentUser } = useUser(); - // derived values - const { fetchWidgetStats, getWidgetStats } = useDashboard(); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const redirectionLink = `/${workspaceSlug}/profile/${currentUser?.id}/activity`; - - useEffect(() => { - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!widgetStats) return ; - - return ( - - - Your work item activities - - {widgetStats.length > 0 ? ( -
- {widgetStats.map((activity) => ( -
-
- {activity.field ? ( - activity.new_value === "restore" ? ( - - ) : ( -
- -
- ) - ) : activity.actor_detail.avatar_url && activity.actor_detail.avatar_url !== "" ? ( - - ) : ( -
- {activity.actor_detail.is_bot - ? activity.actor_detail.first_name.charAt(0) - : activity.actor_detail.display_name.charAt(0)} -
- )} -
-
-

- - {currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail?.display_name}{" "} - - {activity.field ? ( - - ) : ( - - created - - )} -

-

- {calculateTimeAgo(activity.created_at)} -

-
-
- ))} - - View all - -
- ) : ( -
- -
- )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx b/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx deleted file mode 100644 index 415809e0433..00000000000 --- a/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx +++ /dev/null @@ -1,154 +0,0 @@ -"use client"; -import { useState } from "react"; -import sortBy from "lodash/sortBy"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import useSWR from "swr"; -// types -import { TRecentCollaboratorsWidgetResponse } from "@plane/types"; -// ui -import { Avatar } from "@plane/ui"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; -// hooks -import { useDashboard, useMember, useUser } from "@/hooks/store"; -// components -import { WidgetLoader } from "../loaders"; - -type CollaboratorListItemProps = { - issueCount: number; - userId: string; - workspaceSlug: string; -}; - -const CollaboratorListItem: React.FC = observer((props) => { - const { issueCount, userId, workspaceSlug } = props; - // store hooks - const { data: currentUser } = useUser(); - const { getUserDetails } = useMember(); - // derived values - const userDetails = getUserDetails(userId); - const isCurrentUser = userId === currentUser?.id; - - if (!userDetails || userDetails.is_bot) return null; - - return ( - -
- -
-
- {isCurrentUser ? "You" : userDetails?.display_name} -
-

- {issueCount} active work items{issueCount > 1 ? "s" : ""} -

- - ); -}); - -type CollaboratorsListProps = { - dashboardId: string; - searchQuery?: string; - workspaceSlug: string; -}; - -const WIDGET_KEY = "recent_collaborators"; - -export const CollaboratorsList: React.FC = (props) => { - const { dashboardId, searchQuery = "", workspaceSlug } = props; - - // state - const [visibleItems, setVisibleItems] = useState(16); - const [isExpanded, setIsExpanded] = useState(false); - // store hooks - const { fetchWidgetStats } = useDashboard(); - const { getUserDetails } = useMember(); - const { data: currentUser } = useUser(); - - const { data: widgetStats } = useSWR( - workspaceSlug && dashboardId ? `WIDGET_STATS_${workspaceSlug}_${dashboardId}` : null, - workspaceSlug && dashboardId - ? () => - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - }) - : null - ) as { - data: TRecentCollaboratorsWidgetResponse[] | undefined; - }; - - if (!widgetStats) - return ( -
- -
- ); - - const sortedStats = sortBy(widgetStats, [(user) => user?.user_id !== currentUser?.id]); - - const filteredStats = sortedStats.filter((user) => { - if (!user) return false; - const userDetails = getUserDetails(user?.user_id); - if (!userDetails || userDetails.is_bot) return false; - const { display_name, first_name, last_name } = userDetails; - const searchLower = searchQuery.toLowerCase(); - return ( - display_name?.toLowerCase().includes(searchLower) || - first_name?.toLowerCase().includes(searchLower) || - last_name?.toLowerCase().includes(searchLower) - ); - }); - - // Update the displayedStats to always use the visibleItems limit - const handleLoadMore = () => { - setVisibleItems((prev) => { - const newValue = prev + 16; - if (newValue >= filteredStats.length) { - setIsExpanded(true); - return filteredStats.length; - } - return newValue; - }); - }; - - const handleHide = () => { - setVisibleItems(16); - setIsExpanded(false); - }; - - const displayedStats = filteredStats.slice(0, visibleItems); - - return ( - <> -
- {displayedStats?.map((user) => ( - - ))} -
- {filteredStats.length > visibleItems && !isExpanded && ( -
-
- Load more -
-
- )} - {isExpanded && ( -
-
Hide
-
- )} - - ); -}; diff --git a/web/core/components/dashboard/widgets/recent-collaborators/index.ts b/web/core/components/dashboard/widgets/recent-collaborators/index.ts deleted file mode 100644 index 1efe34c51ec..00000000000 --- a/web/core/components/dashboard/widgets/recent-collaborators/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./root"; diff --git a/web/core/components/dashboard/widgets/recent-collaborators/root.tsx b/web/core/components/dashboard/widgets/recent-collaborators/root.tsx deleted file mode 100644 index 4d30642071b..00000000000 --- a/web/core/components/dashboard/widgets/recent-collaborators/root.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useState } from "react"; -import { Search } from "lucide-react"; -// types -import { Card } from "@plane/ui"; -import { WidgetProps } from "@/components/dashboard/widgets"; -// components -import { CollaboratorsList } from "./collaborators-list"; - -export const RecentCollaboratorsWidget: React.FC = (props) => { - const { dashboardId, workspaceSlug } = props; - // states - const [searchQuery, setSearchQuery] = useState(""); - - return ( - -
-
-

Collaborators

-

- View and find all members you collaborate with across projects -

-
-
- - setSearchQuery(e.target.value)} - /> -
-
- -
- ); -}; diff --git a/web/core/components/dashboard/widgets/recent-projects.tsx b/web/core/components/dashboard/widgets/recent-projects.tsx deleted file mode 100644 index 3e9c4e257b9..00000000000 --- a/web/core/components/dashboard/widgets/recent-projects.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -import { Plus } from "lucide-react"; -// plane types -import { PROJECT_BACKGROUND_COLORS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; -import { TRecentProjectsWidgetResponse } from "@plane/types"; -// plane ui -import { Avatar, AvatarGroup, Card } from "@plane/ui"; -// components -import { Logo } from "@/components/common"; -import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets"; -// constants -// helpers -import { getFileURL } from "@/helpers/file.helper"; -// hooks -import { - useEventTracker, - useDashboard, - useProject, - useCommandPalette, - useUserPermissions, - useMember, -} from "@/hooks/store"; -// plane web constants - -const WIDGET_KEY = "recent_projects"; - -type ProjectListItemProps = { - projectId: string; - workspaceSlug: string; -}; - -const ProjectListItem: React.FC = observer((props) => { - const { projectId, workspaceSlug } = props; - // store hooks - const { getProjectById } = useProject(); - const { getUserDetails } = useMember(); - // derived values - const projectDetails = getProjectById(projectId); - - const randomBgColor = PROJECT_BACKGROUND_COLORS[Math.floor(Math.random() * PROJECT_BACKGROUND_COLORS.length)]; - - if (!projectDetails) return null; - - return ( - -
-
- -
-
-
-
- {projectDetails.name} -
-
- - {projectDetails.members?.map((memberId) => { - const userDetails = getUserDetails(memberId); - if (!userDetails) return null; - return ( - - ); - })} - -
-
- - ); -}); - -export const RecentProjectsWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // store hooks - const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); - const { allowPermissions } = useUserPermissions(); - const { fetchWidgetStats, getWidgetStats } = useDashboard(); - // derived values - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const canCreateProject = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.WORKSPACE - ); - - useEffect(() => { - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!widgetStats) return ; - - return ( - - - Recent projects - -
- {canCreateProject && ( - - )} - {widgetStats.map((projectId) => ( - - ))} -
-
- ); -}); diff --git a/web/core/constants/fetch-keys.ts b/web/core/constants/fetch-keys.ts index ec5de760f6f..8273bbe033b 100644 --- a/web/core/constants/fetch-keys.ts +++ b/web/core/constants/fetch-keys.ts @@ -73,79 +73,17 @@ const myIssuesParamsToKey = (params: any) => { return `${assigneesKey}_${createdByKey}_${stateGroupKey}_${subscriberKey}_${priorityKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}`; }; -export const CURRENT_USER = "CURRENT_USER"; -export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS"; export const USER_WORKSPACES_LIST = "USER_WORKSPACES_LIST"; -export const WORKSPACE_DETAILS = (workspaceSlug: string) => `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`; - export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; -export const WORKSPACE_MEMBERS_ME = (workspaceSlug: string) => `WORKSPACE_MEMBERS_ME${workspaceSlug.toUpperCase()}`; -export const WORKSPACE_INVITATIONS = (workspaceSlug: string) => `WORKSPACE_INVITATIONS_${workspaceSlug.toString()}`; -export const WORKSPACE_INVITATION = (invitationId: string) => `WORKSPACE_INVITATION_${invitationId}`; -export const LAST_ACTIVE_WORKSPACE_AND_PROJECTS = "LAST_ACTIVE_WORKSPACE_AND_PROJECTS"; - -export const PROJECTS_LIST = ( - workspaceSlug: string, - params: { - is_favorite: "all" | boolean; - } -) => { - if (!params) return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}`; - return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite.toString().toUpperCase()}`; -}; -export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`; +export const WORKSPACE_INVITATION = (invitationId: string) => `WORKSPACE_INVITATION_${invitationId}`; export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`; -export const PROJECT_INVITATIONS = (projectId: string) => `PROJECT_INVITATIONS_${projectId.toString()}`; - -export const PROJECT_ISSUES_LIST = (workspaceSlug: string, projectId: string) => - `PROJECT_ISSUES_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}`; -export const PROJECT_ISSUES_LIST_WITH_PARAMS = (projectId: string, params?: any) => { - if (!params) return `PROJECT_ISSUES_LIST_WITH_PARAMS_${projectId.toUpperCase()}`; - - const paramsKey = paramsToKey(params); - - return `PROJECT_ISSUES_LIST_WITH_PARAMS_${projectId.toUpperCase()}_${paramsKey}`; -}; -export const PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS = (projectId: string, params?: any) => { - if (!params) return `PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS_${projectId.toUpperCase()}`; - - const paramsKey = paramsToKey(params); - - return `PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS_${projectId.toUpperCase()}_${paramsKey}`; -}; - -export const PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS = (projectId: string, params?: any) => { - if (!params) return `PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS${projectId.toUpperCase()}`; - - const paramsKey = paramsToKey(params); - return `PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS${projectId.toUpperCase()}_${paramsKey}`; -}; - -export const GLOBAL_VIEWS_LIST = (workspaceSlug: string) => `GLOBAL_VIEWS_LIST_${workspaceSlug.toUpperCase()}`; -export const GLOBAL_VIEW_DETAILS = (globalViewId: string) => `GLOBAL_VIEW_DETAILS_${globalViewId.toUpperCase()}`; -export const GLOBAL_VIEW_ISSUES = (globalViewId: string) => `GLOBAL_VIEW_ISSUES_${globalViewId.toUpperCase()}`; - -export const PROJECT_ISSUES_DETAILS = (issueId: string) => `PROJECT_ISSUES_DETAILS_${issueId.toUpperCase()}`; -export const PROJECT_ISSUES_PROPERTIES = (projectId: string) => `PROJECT_ISSUES_PROPERTIES_${projectId.toUpperCase()}`; -export const PROJECT_ISSUES_COMMENTS = (issueId: string) => `PROJECT_ISSUES_COMMENTS_${issueId.toUpperCase()}`; -export const PROJECT_ISSUES_ACTIVITY = (issueId: string) => `PROJECT_ISSUES_ACTIVITY_${issueId.toUpperCase()}`; -export const PROJECT_ISSUE_BY_STATE = (projectId: string) => `PROJECT_ISSUE_BY_STATE_${projectId.toUpperCase()}`; -export const PROJECT_ISSUE_LABELS = (projectId: string) => `PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}`; -export const WORKSPACE_LABELS = (workspaceSlug: string) => `WORKSPACE_LABELS_${workspaceSlug.toUpperCase()}`; export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`; // cycles -export const CYCLES_LIST = (projectId: string) => `CYCLE_LIST_${projectId.toUpperCase()}`; -export const INCOMPLETE_CYCLES_LIST = (projectId: string) => `INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`; -export const CURRENT_CYCLE_LIST = (projectId: string) => `CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`; -export const UPCOMING_CYCLES_LIST = (projectId: string) => `UPCOMING_CYCLES_LIST_${projectId.toUpperCase()}`; -export const DRAFT_CYCLES_LIST = (projectId: string) => `DRAFT_CYCLES_LIST_${projectId.toUpperCase()}`; -export const COMPLETED_CYCLES_LIST = (projectId: string) => `COMPLETED_CYCLES_LIST_${projectId.toUpperCase()}`; -export const CYCLE_ISSUES = (cycleId: string) => `CYCLE_ISSUES_${cycleId.toUpperCase()}`; export const CYCLE_ISSUES_WITH_PARAMS = (cycleId: string, params?: any) => { if (!params) return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}`; @@ -153,47 +91,11 @@ export const CYCLE_ISSUES_WITH_PARAMS = (cycleId: string, params?: any) => { return `CYCLE_ISSUES_WITH_PARAMS_${cycleId.toUpperCase()}_${paramsKey.toUpperCase()}`; }; -export const CYCLE_DETAILS = (cycleId: string) => `CYCLE_DETAILS_${cycleId.toUpperCase()}`; - -export const STATES_LIST = (projectId: string) => `STATES_LIST_${projectId.toUpperCase()}`; - -export const USER_ISSUE = (workspaceSlug: string) => `USER_ISSUE_${workspaceSlug.toUpperCase()}`; -export const USER_ISSUES = (workspaceSlug: string, params: any) => { - const paramsKey = myIssuesParamsToKey(params); - return `USER_ISSUES_${workspaceSlug.toUpperCase()}_${paramsKey}`; -}; export const USER_ACTIVITY = (params: { cursor?: string }) => `USER_ACTIVITY_${params?.cursor}`; -export const USER_WORKSPACE_DASHBOARD = (workspaceSlug: string) => - `USER_WORKSPACE_DASHBOARD_${workspaceSlug.toUpperCase()}`; -export const USER_PROJECT_VIEW = (projectId: string) => `USER_PROJECT_VIEW_${projectId.toUpperCase()}`; - -export const MODULE_LIST = (projectId: string) => `MODULE_LIST_${projectId.toUpperCase()}`; -export const MODULE_ISSUES = (moduleId: string) => `MODULE_ISSUES_${moduleId.toUpperCase()}`; -export const MODULE_ISSUES_WITH_PARAMS = (moduleId: string, params?: any) => { - if (!params) return `MODULE_ISSUES_WITH_PARAMS_${moduleId.toUpperCase()}`; - - const paramsKey = paramsToKey(params); - - return `MODULE_ISSUES_WITH_PARAMS_${moduleId.toUpperCase()}_${paramsKey.toUpperCase()}`; -}; -export const MODULE_DETAILS = (moduleId: string) => `MODULE_DETAILS_${moduleId.toUpperCase()}`; - -export const VIEWS_LIST = (projectId: string) => `VIEWS_LIST_${projectId.toUpperCase()}`; -export const VIEW_DETAILS = (viewId: string) => `VIEW_DETAILS_${viewId.toUpperCase()}`; -export const VIEW_ISSUES = (viewId: string, params: any) => { - if (!params) return `VIEW_ISSUES_${viewId.toUpperCase()}`; - - const paramsKey = paramsToKey(params); - - return `VIEW_ISSUES_${viewId.toUpperCase()}_${paramsKey.toUpperCase()}`; -}; // Issues export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`; -export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`; -export const ISSUE_ATTACHMENTS = (issueId: string) => `ISSUE_ATTACHMENTS_${issueId.toUpperCase()}`; -export const ARCHIVED_ISSUE_DETAILS = (issueId: string) => `ARCHIVED_ISSUE_DETAILS_${issueId.toUpperCase()}`; // integrations export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; @@ -222,21 +124,6 @@ export const GITHUB_REPOSITORY_INFO = (workspaceSlug: string, repoName: string) export const SLACK_CHANNEL_INFO = (workspaceSlug: string, projectId: string) => `SLACK_CHANNEL_INFO_${workspaceSlug.toString().toUpperCase()}_${projectId.toUpperCase()}`; -// Pages -export const RECENT_PAGES_LIST = (projectId: string) => `RECENT_PAGES_LIST_${projectId.toUpperCase()}`; -export const ALL_PAGES_LIST = (projectId: string) => `ALL_PAGES_LIST_${projectId.toUpperCase()}`; -export const ARCHIVED_PAGES_LIST = (projectId: string) => `ARCHIVED_PAGES_LIST_${projectId.toUpperCase}`; -export const FAVORITE_PAGES_LIST = (projectId: string) => `FAVORITE_PAGES_LIST_${projectId.toUpperCase()}`; -export const PRIVATE_PAGES_LIST = (projectId: string) => `PRIVATE_PAGES_LIST_${projectId.toUpperCase()}`; -export const SHARED_PAGES_LIST = (projectId: string) => `SHARED_PAGES_LIST_${projectId.toUpperCase()}`; -export const PAGE_DETAILS = (pageId: string) => `PAGE_DETAILS_${pageId.toUpperCase()}`; -export const PAGE_BLOCKS_LIST = (pageId: string) => `PAGE_BLOCK_LIST_${pageId.toUpperCase()}`; -export const PAGE_BLOCK_DETAILS = (pageId: string) => `PAGE_BLOCK_DETAILS_${pageId.toUpperCase()}`; -export const MY_PAGES_LIST = (pageId: string) => `MY_PAGE_LIST_${pageId}`; -// estimates -export const ESTIMATES_LIST = (projectId: string) => `ESTIMATES_LIST_${projectId.toUpperCase()}`; -export const ESTIMATE_DETAILS = (estimateId: string) => `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; - // analytics export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) => `ANALYTICS${workspaceSlug.toUpperCase()}_${params.x_axis}_${params.y_axis}_${ @@ -257,19 +144,6 @@ export const USER_PROFILE_ACTIVITY = ( ) => `USER_WORKSPACE_PROFILE_ACTIVITY_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}_${params?.cursor}`; export const USER_PROFILE_PROJECT_SEGREGATION = (workspaceSlug: string, userId: string) => `USER_PROFILE_PROJECT_SEGREGATION_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}`; -export const USER_PROFILE_ISSUES = (workspaceSlug: string, userId: string, params: any) => { - const paramsKey = myIssuesParamsToKey(params); - - return `USER_PROFILE_ISSUES_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}_${paramsKey}`; -}; - -// reactions -export const ISSUE_REACTION_LIST = (workspaceSlug: string, projectId: string, issueId: string) => - `ISSUE_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${issueId.toUpperCase()}`; -export const COMMENT_REACTION_LIST = (workspaceSlug: string, projectId: string, commendId: string) => - `COMMENT_REACTION_LIST_${workspaceSlug.toUpperCase()}_${projectId.toUpperCase()}_${commendId.toUpperCase()}`; // api-tokens export const API_TOKENS_LIST = (workspaceSlug: string) => `API_TOKENS_LIST_${workspaceSlug.toUpperCase()}`; -export const API_TOKEN_DETAILS = (workspaceSlug: string, tokenId: string) => - `API_TOKEN_DETAILS_${workspaceSlug.toUpperCase()}_${tokenId.toUpperCase()}`; diff --git a/web/helpers/string.helper.ts b/web/helpers/string.helper.ts index d0973109171..4c85e3ca84a 100644 --- a/web/helpers/string.helper.ts +++ b/web/helpers/string.helper.ts @@ -1,10 +1,4 @@ import DOMPurify from "isomorphic-dompurify"; -import { - CYCLE_ISSUES_WITH_PARAMS, - MODULE_ISSUES_WITH_PARAMS, - PROJECT_ISSUES_LIST_WITH_PARAMS, - VIEW_ISSUES, -} from "@/constants/fetch-keys"; export const addSpaceIfCamelCase = (str: string) => { if (str === undefined || str === null) return ""; @@ -150,29 +144,6 @@ export const objToQueryParams = (obj: any) => { return params.toString(); }; -export const getFetchKeysForIssueMutation = (options: { - cycleId?: string | string[]; - moduleId?: string | string[]; - viewId?: string | string[]; - projectId: string; - viewGanttParams: any; - ganttParams: any; -}) => { - const { cycleId, moduleId, viewId, projectId, viewGanttParams, ganttParams } = options; - - const ganttFetchKey = cycleId - ? { ganttFetchKey: CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), ganttParams) } - : moduleId - ? { ganttFetchKey: MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), ganttParams) } - : viewId - ? { ganttFetchKey: VIEW_ISSUES(viewId.toString(), viewGanttParams) } - : { ganttFetchKey: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", ganttParams) }; - - return { - ...ganttFetchKey, - }; -}; - /** * @returns {boolean} true if searchQuery is substring of text in the same order, false otherwise * @description Returns true if searchQuery is substring of text in the same order, false otherwise diff --git a/web/package.json b/web/package.json index 7eb12c8ac87..723474945fa 100644 --- a/web/package.json +++ b/web/package.json @@ -19,7 +19,6 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.1.3", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^1.3.0", "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", - "@blueprintjs/popover2": "^1.13.3", "@headlessui/react": "^1.7.3", "@intercom/messenger-js-sdk": "^0.0.12", "@nivo/bar": "^0.88.0", @@ -72,7 +71,6 @@ "smooth-scroll-into-view-if-needed": "^2.0.2", "swr": "^2.1.3", "tailwind-merge": "^2.0.0", - "use-debounce": "^9.0.4", "use-font-face-observer": "^1.2.2", "uuid": "^9.0.0", "zxcvbn": "^4.4.2" From 533644f3f177a75780a6633e138c804162393901 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 3 Jun 2025 19:28:19 +0530 Subject: [PATCH 2/3] refactor: remove unused hooks and wrappers --- web/core/components/page-views/index.ts | 1 - .../page-views/workspace-dashboard.tsx | 118 ------------------ web/core/hooks/use-comment-reaction.tsx | 93 -------------- web/core/hooks/use-dynamic-dropdown.tsx | 63 ---------- web/core/hooks/use-url-hash.tsx | 14 --- web/core/lib/wrappers/crisp-wrapper.tsx | 41 ------ 6 files changed, 330 deletions(-) delete mode 100644 web/core/components/page-views/index.ts delete mode 100644 web/core/components/page-views/workspace-dashboard.tsx delete mode 100644 web/core/hooks/use-comment-reaction.tsx delete mode 100644 web/core/hooks/use-dynamic-dropdown.tsx delete mode 100644 web/core/hooks/use-url-hash.tsx delete mode 100644 web/core/lib/wrappers/crisp-wrapper.tsx diff --git a/web/core/components/page-views/index.ts b/web/core/components/page-views/index.ts deleted file mode 100644 index 0491e8caf53..00000000000 --- a/web/core/components/page-views/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./workspace-dashboard"; diff --git a/web/core/components/page-views/workspace-dashboard.tsx b/web/core/components/page-views/workspace-dashboard.tsx deleted file mode 100644 index 19d3590b07f..00000000000 --- a/web/core/components/page-views/workspace-dashboard.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// plane imports -import { EUserPermissionsLevel, PRODUCT_TOUR_COMPLETED, EUserPermissions } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { ContentWrapper } from "@plane/ui"; -// components -import { DashboardWidgets } from "@/components/dashboard"; -import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; -import { IssuePeekOverview } from "@/components/issues"; -import { TourRoot } from "@/components/onboarding"; -import { UserGreetingsView } from "@/components/user"; -// constants -// helpers -import { cn } from "@/helpers/common.helper"; -// hooks -import { - useCommandPalette, - useUserProfile, - useEventTracker, - useDashboard, - useProject, - useUser, - useUserPermissions, -} from "@/hooks/store"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import useSize from "@/hooks/use-window-size"; - -export const WorkspaceDashboardView = observer(() => { - // plane hooks - const { t } = useTranslation(); - // store hooks - const { captureEvent, setTrackElement } = useEventTracker(); - const { toggleCreateProjectModal } = useCommandPalette(); - const { workspaceSlug } = useParams(); - const { data: currentUser } = useUser(); - const { data: currentUserProfile, updateTourCompleted } = useUserProfile(); - const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard(); - const { joinedProjectIds, loader } = useProject(); - const { allowPermissions } = useUserPermissions(); - - // helper hooks - const [windowWidth] = useSize(); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/dashboard" }); - - const handleTourCompleted = () => { - updateTourCompleted() - .then(() => { - captureEvent(PRODUCT_TOUR_COMPLETED, { - user_id: currentUser?.id, - state: "SUCCESS", - }); - }) - .catch((error) => { - console.error(error); - }); - }; - - // fetch home dashboard widgets on workspace change - useEffect(() => { - if (!workspaceSlug) return; - - fetchHomeDashboardWidgets(workspaceSlug?.toString()); - }, [fetchHomeDashboardWidgets, workspaceSlug]); - - const canPerformEmptyStateActions = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.WORKSPACE - ); - - // TODO: refactor loader implementation - return ( - <> - {currentUserProfile && !currentUserProfile.is_tour_completed && ( -
- -
- )} - {homeDashboardId && joinedProjectIds && ( - <> - {joinedProjectIds.length > 0 || loader === "init-loader" ? ( - <> - - = 768, - })} - > - {currentUser && } - - - - - ) : ( - { - setTrackElement("Dashboard empty state"); - toggleCreateProjectModal(true); - }} - disabled={!canPerformEmptyStateActions} - /> - } - /> - )} - - )} - - ); -}); diff --git a/web/core/hooks/use-comment-reaction.tsx b/web/core/hooks/use-comment-reaction.tsx deleted file mode 100644 index 848c9b42654..00000000000 --- a/web/core/hooks/use-comment-reaction.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import useSWR from "swr"; -// fetch keys -import { COMMENT_REACTION_LIST } from "@/constants/fetch-keys"; -// services -import { groupReactions } from "@/helpers/emoji.helper"; -import { useUser } from "@/hooks/store"; -import { IssueReactionService } from "@/services/issue"; -// helpers -// hooks -// services -const issueReactionService = new IssueReactionService(); - -const useCommentReaction: any = ( - workspaceSlug?: string | string[] | null, - projectId?: string | string[] | null, - commendId?: string | string[] | null -) => { - const { - data: commentReactions, - mutate: mutateCommentReactions, - error, - } = useSWR( - workspaceSlug && projectId && commendId - ? COMMENT_REACTION_LIST(workspaceSlug.toString(), projectId.toString(), commendId.toString()) - : null, - workspaceSlug && projectId && commendId - ? () => - issueReactionService.listIssueCommentReactions( - workspaceSlug.toString(), - projectId.toString(), - commendId.toString() - ) - : null - ); - - const { data: currentUser } = useUser(); - - const groupedReactions = groupReactions(commentReactions || [], "reaction"); - - /** - * @description Use this function to create user's reaction to an issue. This function will mutate the reactions state. - * @param {string} reaction - * @example handleReactionDelete("123") // 123 -> is emoji hexa-code - */ - - const handleReactionCreate = async (reaction: string) => { - if (!workspaceSlug || !projectId || !commendId) return; - - const data = await issueReactionService.createIssueCommentReaction( - workspaceSlug.toString(), - projectId.toString(), - commendId.toString(), - { reaction } - ); - - mutateCommentReactions((prev: any) => [...(prev || []), data]); - }; - - /** - * @description Use this function to delete user's reaction from an issue. This function will mutate the reactions state. - * @param {string} reaction - * @example handleReactionDelete("123") // 123 -> is emoji hexa-code - */ - - const handleReactionDelete = async (reaction: string) => { - if (!workspaceSlug || !projectId || !commendId) return; - - mutateCommentReactions( - (prevData: any) => prevData?.filter((r: any) => r.actor !== currentUser?.id || r.reaction !== reaction) || [], - false - ); - - await issueReactionService.deleteIssueCommentReaction( - workspaceSlug.toString(), - projectId.toString(), - commendId.toString(), - reaction - ); - - mutateCommentReactions(); - }; - - return { - isLoading: !commentReactions && !error, - commentReactions, - groupedReactions, - handleReactionCreate, - handleReactionDelete, - mutateCommentReactions, - } as const; -}; - -export default useCommentReaction; diff --git a/web/core/hooks/use-dynamic-dropdown.tsx b/web/core/hooks/use-dynamic-dropdown.tsx deleted file mode 100644 index 771763e3585..00000000000 --- a/web/core/hooks/use-dynamic-dropdown.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback, useEffect } from "react"; -// plane helpers -import { useOutsideClickDetector } from "@plane/hooks"; - -/** - * Custom hook for dynamic dropdown position calculation. - * @param isOpen - Indicates whether the dropdown is open. - * @param handleClose - Callback to handle closing the dropdown. - * @param buttonRef - Ref object for the button triggering the dropdown. - * @param dropdownRef - Ref object for the dropdown element. - */ - -const useDynamicDropdownPosition = ( - isOpen: boolean, - handleClose: () => void, - buttonRef: React.RefObject, - dropdownRef: React.RefObject -) => { - const handlePosition = useCallback(() => { - const button = buttonRef.current; - const dropdown = dropdownRef.current; - - if (!dropdown || !button) return; - - const buttonRect = button.getBoundingClientRect(); - const dropdownRect = dropdown.getBoundingClientRect(); - - const { innerHeight, innerWidth, scrollX, scrollY } = window; - - let top: number = buttonRect.bottom + scrollY; - if (top + dropdownRect.height > innerHeight) top = innerHeight - dropdownRect.height; - - let left: number = buttonRect.left + scrollX + (buttonRect.width - dropdownRect.width) / 2; - if (left + dropdownRect.width > innerWidth) left = innerWidth - dropdownRect.width; - - dropdown.style.top = `${Math.max(top, 5)}px`; - dropdown.style.left = `${Math.max(left, 5)}px`; - }, [buttonRef, dropdownRef]); - - useEffect(() => { - if (isOpen) handlePosition(); - }, [handlePosition, isOpen]); - - useOutsideClickDetector(dropdownRef, () => { - if (isOpen) handleClose(); - }); - - const handleResize = useCallback(() => { - if (isOpen) { - handlePosition(); - } - }, [handlePosition, isOpen]); - - useEffect(() => { - window.addEventListener("resize", handleResize); - - return () => { - window.removeEventListener("resize", handleResize); - }; - }, [isOpen, handleResize]); -}; - -export default useDynamicDropdownPosition; diff --git a/web/core/hooks/use-url-hash.tsx b/web/core/hooks/use-url-hash.tsx deleted file mode 100644 index 9dc6d1770fa..00000000000 --- a/web/core/hooks/use-url-hash.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect, useState } from "react"; - -const useURLHash = () => { - const [hashValue, setHashValue] = useState(); - - useEffect(() => { - const hash = window.location.hash?.split("#")[1]; - setHashValue(hash); - }, []); - - return hashValue; -}; - -export default useURLHash; diff --git a/web/core/lib/wrappers/crisp-wrapper.tsx b/web/core/lib/wrappers/crisp-wrapper.tsx deleted file mode 100644 index 1debf8ea5bd..00000000000 --- a/web/core/lib/wrappers/crisp-wrapper.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect, ReactNode, FC } from "react"; -import { observer } from "mobx-react"; -// hooks -import { useUser } from "@/hooks/store"; - -declare global { - interface Window { - $crisp: unknown[]; - CRISP_WEBSITE_ID: unknown; - } -} - -export interface ICrispWrapper { - children: ReactNode; -} - -const CrispWrapper: FC = observer((props) => { - const { children } = props; - const { data: user } = useUser(); - - useEffect(() => { - if (typeof window && user?.email && process.env.NEXT_PUBLIC_CRISP_ID) { - window.$crisp = []; - window.CRISP_WEBSITE_ID = process.env.NEXT_PUBLIC_CRISP_ID; - (function () { - const d = document; - const s = d.createElement("script"); - s.src = "https://client.crisp.chat/l.js"; - s.async = true; - d.getElementsByTagName("head")[0].appendChild(s); - window.$crisp.push(["set", "user:email", [user.email]]); - window.$crisp.push(["do", "chat:hide"]); - window.$crisp.push(["do", "chat:close"]); - })(); - } - }, [user?.email]); - - return <>{children}; -}); - -export default CrispWrapper; From 664820199265ab0228ba9a346c311ba25986e161 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 6 Jun 2025 13:58:27 +0530 Subject: [PATCH 3/3] chore: remove unused function --- web/core/constants/fetch-keys.ts | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/web/core/constants/fetch-keys.ts b/web/core/constants/fetch-keys.ts index 8273bbe033b..7bf2e30eed2 100644 --- a/web/core/constants/fetch-keys.ts +++ b/web/core/constants/fetch-keys.ts @@ -47,38 +47,14 @@ const paramsToKey = (params: any) => { return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${subscriberKey}`; }; -const myIssuesParamsToKey = (params: any) => { - const { assignees, created_by, labels, priority, state_group, subscriber, start_date, target_date } = params; - - let assigneesKey = assignees ? assignees.split(",") : []; - let createdByKey = created_by ? created_by.split(",") : []; - let stateGroupKey = state_group ? state_group.split(",") : []; - let subscriberKey = subscriber ? subscriber.split(",") : []; - let priorityKey = priority ? priority.split(",") : []; - let labelsKey = labels ? labels.split(",") : []; - const startDateKey = start_date ?? ""; - const targetDateKey = target_date ?? ""; - const type = params?.type ? params.type.toUpperCase() : "NULL"; - const groupBy = params?.group_by ? params.group_by.toUpperCase() : "NULL"; - const orderBy = params?.order_by ? params.order_by.toUpperCase() : "NULL"; - - // sorting each keys in ascending order - assigneesKey = assigneesKey.sort().join("_"); - createdByKey = createdByKey.sort().join("_"); - stateGroupKey = stateGroupKey.sort().join("_"); - subscriberKey = subscriberKey.sort().join("_"); - priorityKey = priorityKey.sort().join("_"); - labelsKey = labelsKey.sort().join("_"); - - return `${assigneesKey}_${createdByKey}_${stateGroupKey}_${subscriberKey}_${priorityKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}`; -}; - export const USER_WORKSPACES_LIST = "USER_WORKSPACES_LIST"; export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; export const WORKSPACE_INVITATION = (invitationId: string) => `WORKSPACE_INVITATION_${invitationId}`; +export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`; + export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`; export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`;