[WEB-5358] fix: analytics enhancements (#8150)

* refactor: enhance analytics page tab functionality and UI

- Introduced state management for selected tabs using useState and useEffect hooks.
- Updated tab handling to improve user experience with onValueChange for Tabs component.
- Refactored AnalyticsFilterActions to remove unused duration selection.
- Added Tooltip to project name in ActiveProjectItem for better visibility.
- Minor styling adjustments in Tabs component for improved layout consistency.

* refactor: improve tab layout in analytics page by adding w-fit

* fix: update styling for ActiveProjectItem component

* refactor: simplify analytics page and introduce useAnalyticsTabs hook

- Removed unused imports and optimized the analytics page component.
- Replaced getAnalyticsTabs with a new custom hook useAnalyticsTabs for better modularity.
- Added useAnalyticsTabs hook to manage analytics tab logic.
- Created new files for useAnalyticsTabs and analytics tabs in the 'ce' and 'ee' directories.

* fix: ensure consistent export in use-analytics-tabs file

* style: format code for better readability in AnalyticsPage component

* fix: add missing newline at end of file in use-analytics-tabs

* chore: remove unused analytics tab components

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Jayash Tripathy 2025-11-24 21:29:34 +05:30 committed by GitHub
parent 09f4e3d4ae
commit 2804987fe8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 45 deletions

View file

@ -1,14 +1,15 @@
import { useMemo } from "react"; "use client";
import { useState, useEffect } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
// plane package imports // plane package imports
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state"; import { EmptyStateDetailed } from "@plane/propel/empty-state";
import type { AnalyticsTab } from "@plane/types"; import { Tabs } from "@plane/propel/tabs";
import { Tabs } from "@plane/ui";
import type { TabItem } from "@plane/ui";
// components // components
import { cn } from "@plane/utils";
import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions"; import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
// hooks // hooks
@ -17,7 +18,7 @@ import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs"; import { useAnalyticsTabs } from "@/plane-web/components/analytics/use-analytics-tabs";
import type { Route } from "./+types/page"; import type { Route } from "./+types/page";
function AnalyticsPage({ params }: Route.ComponentProps) { function AnalyticsPage({ params }: Route.ComponentProps) {
@ -35,31 +36,32 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
const pageTitle = currentWorkspace?.name
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
: undefined;
// permissions // permissions
const canPerformEmptyStateActions = allowPermissions( const canPerformEmptyStateActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER], [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE EUserPermissionsLevel.WORKSPACE
); );
// derived values const workspaceSlug = params.workspaceSlug;
const pageTitle = currentWorkspace?.name const ANALYTICS_TABS = useAnalyticsTabs(workspaceSlug.toString());
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
: undefined; const [selectedTab, setSelectedTab] = useState(tabId || ANALYTICS_TABS[0]?.key);
const ANALYTICS_TABS = useMemo<AnalyticsTab[]>(() => getAnalyticsTabs(t), [t]);
const tabs: TabItem[] = useMemo( useEffect(() => {
() => if (tabId) {
ANALYTICS_TABS.map((tab) => ({ setSelectedTab(tabId);
key: tab.key, }
label: tab.label, }, [tabId]);
content: <tab.content />,
onClick: () => { // Handle tab change
router.push(`/${currentWorkspace?.slug}/analytics/${tab.key}`); const handleTabChange = (value: string) => {
}, setSelectedTab(value);
disabled: tab.isDisabled, router.push(`/${currentWorkspace?.slug}/analytics/${value}`);
})), };
[ANALYTICS_TABS, router, currentWorkspace?.slug]
);
const defaultTab = tabId;
return ( return (
<> <>
@ -67,19 +69,43 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
{workspaceProjectIds && ( {workspaceProjectIds && (
<> <>
{workspaceProjectIds.length > 0 || loader === "init-loader" ? ( {workspaceProjectIds.length > 0 || loader === "init-loader" ? (
<div className="flex h-full overflow-hidden bg-custom-background-100 justify-between items-center "> <div className="flex h-full overflow-hidden bg-custom-background-100 ">
<Tabs <Tabs value={selectedTab} onValueChange={handleTabChange} className="w-full h-full">
tabs={tabs} <div className={"flex flex-col w-full h-full"}>
storageKey={`analytics-page-${currentWorkspace?.id}`} <div
defaultTab={defaultTab} className={cn(
size="md" "px-6 py-2 border-b border-custom-border-200 flex items-center gap-4 overflow-hidden w-full justify-between"
tabListContainerClassName="px-6 py-2 border-b border-custom-border-200 flex items-center justify-between" )}
tabListClassName="my-2 w-auto" >
tabClassName="px-3" <Tabs.List className={"my-2 overflow-x-auto flex w-fit"}>
tabPanelClassName="h-full overflow-hidden overflow-y-auto px-2" {ANALYTICS_TABS.map((tab) => (
storeInLocalStorage={false} <Tabs.Trigger
actions={<AnalyticsFilterActions />} key={tab.key}
/> value={tab.key}
disabled={tab.isDisabled}
size="md"
className="px-3"
>
{tab.label}
</Tabs.Trigger>
))}
</Tabs.List>
<div className="flex-shrink-0">
<AnalyticsFilterActions />
</div>
</div>
{ANALYTICS_TABS.map((tab) => (
<Tabs.Content
key={tab.key}
value={tab.key}
className={"h-full overflow-hidden overflow-y-auto px-2"}
>
<tab.content />
</Tabs.Content>
))}
</div>
</Tabs>
</div> </div>
) : ( ) : (
<EmptyStateDetailed <EmptyStateDetailed

View file

@ -0,0 +1,11 @@
import { useMemo } from "react";
import { useTranslation } from "@plane/i18n";
import { getAnalyticsTabs } from "./tabs";
export const useAnalyticsTabs = (workspaceSlug: string) => {
const { t } = useTranslation();
const analyticsTabs = useMemo(() => getAnalyticsTabs(t), [t]);
return analyticsTabs;
};

View file

@ -4,11 +4,10 @@ import { observer } from "mobx-react";
import { useAnalytics } from "@/hooks/store/use-analytics"; import { useAnalytics } from "@/hooks/store/use-analytics";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
// components // components
import DurationDropdown from "./select/duration";
import { ProjectSelect } from "./select/project"; import { ProjectSelect } from "./select/project";
const AnalyticsFilterActions = observer(function AnalyticsFilterActions() { const AnalyticsFilterActions = observer(function AnalyticsFilterActions() {
const { selectedProjects, selectedDuration, updateSelectedProjects, updateSelectedDuration } = useAnalytics(); const { selectedProjects, updateSelectedProjects } = useAnalytics();
const { joinedProjectIds } = useProject(); const { joinedProjectIds } = useProject();
return ( return (
<div className="flex items-center justify-end gap-2"> <div className="flex items-center justify-end gap-2">

View file

@ -1,6 +1,7 @@
// plane package imports // plane package imports
import { Logo } from "@plane/propel/emoji-icon-picker"; import { Logo } from "@plane/propel/emoji-icon-picker";
import { ProjectIcon } from "@plane/propel/icons"; import { ProjectIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// plane web hooks // plane web hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
@ -33,9 +34,9 @@ function ActiveProjectItem(props: Props) {
if (!projectDetails) return null; if (!projectDetails) return null;
return ( return (
<div className="flex items-center justify-between gap-2 "> <div className="flex items-center justify-between gap-2 w-full">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 flex-1 overflow-hidden">
<div className="flex h-8 w-8 items-center justify-center rounded-xl bg-custom-background-80"> <div className="flex h-8 w-8 items-center justify-center rounded-xl bg-custom-background-80 shrink-0">
<span className="grid h-4 w-4 flex-shrink-0 place-items-center"> <span className="grid h-4 w-4 flex-shrink-0 place-items-center">
{projectDetails?.logo_props ? ( {projectDetails?.logo_props ? (
<Logo logo={projectDetails?.logo_props} size={16} /> <Logo logo={projectDetails?.logo_props} size={16} />
@ -46,13 +47,15 @@ function ActiveProjectItem(props: Props) {
)} )}
</span> </span>
</div> </div>
<p className="text-sm font-medium">{projectDetails?.name}</p> <Tooltip tooltipContent={projectDetails?.name} position="top-start">
<p className="text-sm font-medium truncate">{projectDetails?.name}</p>
</Tooltip>
</div> </div>
<CompletionPercentage <CompletionPercentage
percentage={completed_issues && total_issues ? Math.round((completed_issues / total_issues) * 100) : 0} percentage={completed_issues && total_issues ? Math.round((completed_issues / total_issues) * 100) : 0}
/> />
</div> </div>
); );
} };
export default ActiveProjectItem; export default ActiveProjectItem;

View file

@ -41,7 +41,7 @@ const TabsList = React.forwardRef(function TabsList(
<TabsPrimitive.List <TabsPrimitive.List
data-slot="tabs-list" data-slot="tabs-list"
className={cn( className={cn(
"flex w-full min-w-fit items-center justify-between gap-1.5 rounded-md text-sm p-0.5 bg-custom-background-80/60 relative overflow-auto", "flex w-full items-center justify-between gap-1.5 rounded-md text-sm p-0.5 bg-custom-background-80/60 relative overflow-auto",
className className
)} )}
{...props} {...props}