chore: revamped the analytics for cycle and module in peek view. (#7075)
* chore: added cycles and modules in analytics peek view * chore: added cycles and modules analytics * chore: added project filter for work items * chore: added a peekview flag and based on that table columns * chore: added peek view * chore: added check for display name * chore: cleaned up some code * chore: fixed export csv data * chore: added distinct work items * chore: assignee in peek view * updated csv fields * chore: updated workitems peek with assignee * fix: removed type assersions for workspaceslug * chore: added day wise filter in cycles and modules * chore: added extra validations --------- Co-authored-by: JayashTripathy <jayashtripathy371@gmail.com>
This commit is contained in:
parent
ba158d5d6e
commit
5b776392bd
21 changed files with 642 additions and 236 deletions
|
|
@ -21,7 +21,7 @@ export const InsightTable = <T extends Exclude<TAnalyticsTabsV2Base, "overview">
|
|||
const { data, isLoading, columns, columnsLabels } = props;
|
||||
const params = useParams();
|
||||
const { t } = useTranslation();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
if (isLoading) {
|
||||
return <TableLoader columns={columns} rows={5} />;
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ export const InsightTable = <T extends Exclude<TAnalyticsTabsV2Base, "overview">
|
|||
|
||||
const exportCSV = (rows: Row<AnalyticsTableDataMap[T]>[]) => {
|
||||
const rowData: any = rows.map((row) => {
|
||||
const { project_id, ...exportableData } = row.original;
|
||||
const { project_id, avatar_url, assignee_id, ...exportableData } = row.original;
|
||||
return Object.fromEntries(
|
||||
Object.entries(exportableData).map(([key, value]) => {
|
||||
if (columnsLabels?.[key]) {
|
||||
|
|
|
|||
|
|
@ -26,16 +26,20 @@ const analyticsV2Service = new AnalyticsV2Service();
|
|||
const ProjectInsights = observer(() => {
|
||||
const params = useParams();
|
||||
const { t } = useTranslation();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const { selectedDuration, selectedDurationLabel, selectedProjects } = useAnalyticsV2();
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } =
|
||||
useAnalyticsV2();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-radar" });
|
||||
|
||||
const { data: projectInsightsData, isLoading: isLoadingProjectInsight } = useSWR(
|
||||
`radar-chart-${workspaceSlug}-${selectedDuration}-${selectedProjects}`,
|
||||
`radar-chart-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
|
||||
() =>
|
||||
analyticsV2Service.getAdvanceAnalyticsCharts<TChartData<string, string>[]>(workspaceSlug, "projects", {
|
||||
// date_filter: selectedDuration,
|
||||
...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
|
||||
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
|
||||
...(selectedModule ? { module_id: selectedModule } : {}),
|
||||
...(isPeekView ? { peek_view: true } : {}),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,20 @@ const analyticsV2Service = new AnalyticsV2Service();
|
|||
const TotalInsights: React.FC<{ analyticsType: TAnalyticsTabsV2Base; peekView?: boolean }> = observer(
|
||||
({ analyticsType, peekView }) => {
|
||||
const params = useParams();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const { t } = useTranslation();
|
||||
const { selectedDuration, selectedProjects, selectedDurationLabel } = useAnalyticsV2();
|
||||
const { selectedDuration, selectedProjects, selectedDurationLabel, selectedCycle, selectedModule, isPeekView } =
|
||||
useAnalyticsV2();
|
||||
|
||||
const { data: totalInsightsData, isLoading } = useSWR(
|
||||
`total-insights-${analyticsType}-${selectedDuration}-${selectedProjects}`,
|
||||
`total-insights-${analyticsType}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
|
||||
() =>
|
||||
analyticsV2Service.getAdvanceAnalytics<IAnalyticsResponseV2>(workspaceSlug, analyticsType, {
|
||||
// date_filter: selectedDuration,
|
||||
...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}),
|
||||
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
|
||||
...(selectedModule ? { module_id: selectedModule } : {}),
|
||||
...(isPeekView ? { peek_view: true } : {}),
|
||||
})
|
||||
);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -19,17 +19,21 @@ import { ChartLoader } from "../loaders";
|
|||
|
||||
const analyticsV2Service = new AnalyticsV2Service();
|
||||
const CreatedVsResolved = observer(() => {
|
||||
const { selectedDuration, selectedDurationLabel, selectedProjects } = useAnalyticsV2();
|
||||
const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } =
|
||||
useAnalyticsV2();
|
||||
const params = useParams();
|
||||
const { t } = useTranslation();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-area" });
|
||||
const { data: createdVsResolvedData, isLoading: isCreatedVsResolvedLoading } = useSWR(
|
||||
`created-vs-resolved-${workspaceSlug}-${selectedDuration}-${selectedProjects}`,
|
||||
`created-vs-resolved-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
|
||||
() =>
|
||||
analyticsV2Service.getAdvanceAnalyticsCharts<IChartResponseV2>(workspaceSlug, "work-items", {
|
||||
// date_filter: selectedDuration,
|
||||
...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
|
||||
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
|
||||
...(selectedModule ? { module_id: selectedModule } : {}),
|
||||
...(isPeekView ? { peek_view: true } : {}),
|
||||
})
|
||||
);
|
||||
const parsedData: TChartData<string, string>[] = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { IProject } from "@plane/types";
|
||||
import { ICycle, IModule, IProject } from "@plane/types";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// hooks
|
||||
import { useAnalyticsV2 } from "@/hooks/store";
|
||||
|
|
@ -15,20 +15,52 @@ import WorkItemsInsightTable from "../workitems-insight-table";
|
|||
type Props = {
|
||||
fullScreen: boolean;
|
||||
projectDetails: IProject | undefined;
|
||||
cycleDetails: ICycle | undefined;
|
||||
moduleDetails: IModule | undefined;
|
||||
};
|
||||
|
||||
export const WorkItemsModalMainContent: React.FC<Props> = observer((props) => {
|
||||
const { projectDetails, fullScreen } = props;
|
||||
const { updateSelectedProjects } = useAnalyticsV2();
|
||||
const [isProjectConfigured, setIsProjectConfigured] = useState(false);
|
||||
const { projectDetails, cycleDetails, moduleDetails, fullScreen } = props;
|
||||
const { updateSelectedProjects, updateSelectedCycle, updateSelectedModule, updateIsPeekView } = useAnalyticsV2();
|
||||
const [isModalConfigured, setIsModalConfigured] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectDetails?.id) return;
|
||||
updateSelectedProjects([projectDetails?.id ?? ""]);
|
||||
setIsProjectConfigured(true);
|
||||
}, [projectDetails?.id, updateSelectedProjects]);
|
||||
updateIsPeekView(true);
|
||||
|
||||
if (!isProjectConfigured)
|
||||
// Handle project selection
|
||||
if (projectDetails?.id) {
|
||||
updateSelectedProjects([projectDetails.id]);
|
||||
}
|
||||
|
||||
// Handle cycle selection
|
||||
if (cycleDetails?.id) {
|
||||
updateSelectedCycle(cycleDetails.id);
|
||||
}
|
||||
|
||||
// Handle module selection
|
||||
if (moduleDetails?.id) {
|
||||
updateSelectedModule(moduleDetails.id);
|
||||
}
|
||||
setIsModalConfigured(true);
|
||||
|
||||
// Cleanup fields
|
||||
return () => {
|
||||
updateSelectedProjects([]);
|
||||
updateSelectedCycle("");
|
||||
updateSelectedModule("");
|
||||
updateIsPeekView(false);
|
||||
};
|
||||
}, [
|
||||
projectDetails?.id,
|
||||
cycleDetails?.id,
|
||||
moduleDetails?.id,
|
||||
updateSelectedProjects,
|
||||
updateSelectedCycle,
|
||||
updateSelectedModule,
|
||||
updateIsPeekView,
|
||||
]);
|
||||
|
||||
if (!isModalConfigured)
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Spinner />
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
import { observer } from "mobx-react";
|
||||
|
||||
// icons
|
||||
// plane package imports
|
||||
import { Expand, Shrink, X } from "lucide-react";
|
||||
import { ICycle, IModule } from "@plane/types";
|
||||
// icons
|
||||
|
||||
type Props = {
|
||||
fullScreen: boolean;
|
||||
handleClose: () => void;
|
||||
setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
title: string;
|
||||
cycle?: ICycle;
|
||||
module?: IModule;
|
||||
};
|
||||
|
||||
export const WorkItemsModalHeader: React.FC<Props> = observer((props) => {
|
||||
const { fullScreen, handleClose, setFullScreen, title } = props;
|
||||
const { fullScreen, handleClose, setFullScreen, title, cycle, module } = props;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 bg-custom-background-100 px-5 py-4 text-sm">
|
||||
<h3 className="break-words">Analytics for {title}</h3>
|
||||
<h3 className="break-words">
|
||||
Analytics for {title} {cycle && `in ${cycle.name}`} {module && `in ${module.name}`}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { IProject } from "@plane/types";
|
||||
import { ICycle, IModule, IProject } from "@plane/types";
|
||||
// plane web components
|
||||
import { WorkItemsModalMainContent } from "./content";
|
||||
import { WorkItemsModalHeader } from "./header";
|
||||
|
|
@ -11,10 +11,12 @@ type Props = {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
projectDetails?: IProject | undefined;
|
||||
cycleDetails?: ICycle | undefined;
|
||||
moduleDetails?: IModule | undefined;
|
||||
};
|
||||
|
||||
export const WorkItemsModal: React.FC<Props> = observer((props) => {
|
||||
const { isOpen, onClose, projectDetails } = props;
|
||||
const { isOpen, onClose, projectDetails, moduleDetails, cycleDetails } = props;
|
||||
|
||||
const [fullScreen, setFullScreen] = useState(false);
|
||||
|
||||
|
|
@ -51,8 +53,15 @@ export const WorkItemsModal: React.FC<Props> = observer((props) => {
|
|||
handleClose={handleClose}
|
||||
setFullScreen={setFullScreen}
|
||||
title={projectDetails?.name ?? ""}
|
||||
cycle={cycleDetails}
|
||||
module={moduleDetails}
|
||||
/>
|
||||
<WorkItemsModalMainContent
|
||||
fullScreen={fullScreen}
|
||||
projectDetails={projectDetails}
|
||||
cycleDetails={cycleDetails}
|
||||
moduleDetails={moduleDetails}
|
||||
/>
|
||||
<WorkItemsModalMainContent fullScreen={fullScreen} projectDetails={projectDetails} />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
|
|
|
|||
|
|
@ -46,19 +46,23 @@ const PriorityChart = observer((props: Props) => {
|
|||
const { t } = useTranslation();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-bar" });
|
||||
// store hooks
|
||||
const { selectedDuration, selectedProjects } = useAnalyticsV2();
|
||||
const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView } = useAnalyticsV2();
|
||||
const { workspaceStates } = useProjectState();
|
||||
const { resolvedTheme } = useTheme();
|
||||
// router
|
||||
const params = useParams();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
|
||||
const { data: priorityChartData, isLoading: priorityChartLoading } = useSWR(
|
||||
`customized-insights-chart-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${props.x_axis}-${props.y_axis}-${props.group_by}`,
|
||||
`customized-insights-chart-${workspaceSlug}-${selectedDuration}-
|
||||
${selectedProjects}-${selectedCycle}-${selectedModule}-${props.x_axis}-${props.y_axis}-${props.group_by}-${isPeekView}`,
|
||||
() =>
|
||||
analyticsV2Service.getAdvanceAnalyticsCharts<TChart>(workspaceSlug, "custom-work-items", {
|
||||
// date_filter: selectedDuration,
|
||||
...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
|
||||
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
|
||||
...(selectedModule ? { module_id: selectedModule } : {}),
|
||||
...(isPeekView ? { peek_view: true } : {}),
|
||||
...props,
|
||||
})
|
||||
);
|
||||
|
|
@ -158,10 +162,23 @@ const PriorityChart = observer((props: Props) => {
|
|||
});
|
||||
|
||||
const exportCSV = (rows: Row<TChartDatum>[]) => {
|
||||
const rowData = rows.map((row) => ({
|
||||
name: row.original.name,
|
||||
count: row.original.count,
|
||||
}));
|
||||
const rowData = rows.map((row) => {
|
||||
const hiddenFields = ["key", "avatar_url", "assignee_id", "project_id"];
|
||||
const otherFields = Object.keys(row.original).filter(
|
||||
(key) => key !== "name" && key !== "count" && !hiddenFields.includes(key) && !key.includes("id")
|
||||
);
|
||||
return {
|
||||
name: row.original.name,
|
||||
count: row.original.count,
|
||||
...otherFields.reduce(
|
||||
(acc, key) => {
|
||||
acc[parsedData?.schema[key] ?? key] = row.original[key];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string | number>
|
||||
),
|
||||
};
|
||||
});
|
||||
const csv = generateCsv(csvConfig)(rowData);
|
||||
download(csvConfig)(csv);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { useMemo } from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { Briefcase } from "lucide-react";
|
||||
import { Briefcase, UserRound } from "lucide-react";
|
||||
// plane package imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { WorkItemInsightColumns, AnalyticsTableDataMap } from "@plane/types";
|
||||
// plane web components
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2";
|
||||
|
|
@ -21,44 +23,85 @@ const analyticsV2Service = new AnalyticsV2Service();
|
|||
const WorkItemsInsightTable = observer(() => {
|
||||
// router
|
||||
const params = useParams();
|
||||
const workspaceSlug = params.workspaceSlug as string;
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { selectedDuration, selectedProjects } = useAnalyticsV2();
|
||||
const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView } = useAnalyticsV2();
|
||||
const { data: workItemsData, isLoading } = useSWR(
|
||||
`insights-table-work-items-${workspaceSlug}-${selectedDuration}-${selectedProjects}`,
|
||||
`insights-table-work-items-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
|
||||
() =>
|
||||
analyticsV2Service.getAdvanceAnalyticsStats<WorkItemInsightColumns[]>(workspaceSlug, "work-items", {
|
||||
// date_filter: selectedDuration,
|
||||
...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}),
|
||||
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
|
||||
...(selectedModule ? { module_id: selectedModule } : {}),
|
||||
...(isPeekView ? { peek_view: true } : {}),
|
||||
})
|
||||
);
|
||||
// derived values
|
||||
const columnsLabels: Record<string, string> = {
|
||||
backlog_work_items: t("workspace_projects.state.backlog"),
|
||||
started_work_items: t("workspace_projects.state.started"),
|
||||
un_started_work_items: t("workspace_projects.state.unstarted"),
|
||||
completed_work_items: t("workspace_projects.state.completed"),
|
||||
cancelled_work_items: t("workspace_projects.state.cancelled"),
|
||||
project__name: t("common.project"),
|
||||
};
|
||||
const columnsLabels = useMemo(
|
||||
() => ({
|
||||
backlog_work_items: t("workspace_projects.state.backlog"),
|
||||
started_work_items: t("workspace_projects.state.started"),
|
||||
un_started_work_items: t("workspace_projects.state.unstarted"),
|
||||
completed_work_items: t("workspace_projects.state.completed"),
|
||||
cancelled_work_items: t("workspace_projects.state.cancelled"),
|
||||
project__name: t("common.project"),
|
||||
display_name: t("common.assignee"),
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
accessorKey: "project__name",
|
||||
header: () => <div className="text-left">{columnsLabels["project__name"]}</div>,
|
||||
cell: ({ row }) => {
|
||||
const project = getProjectById(row.original.project_id);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{project?.logo_props ? <Logo logo={project.logo_props} size={18} /> : <Briefcase className="h-4 w-4" />}
|
||||
{project?.name}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
!isPeekView
|
||||
? {
|
||||
accessorKey: "project__name",
|
||||
header: () => <div className="text-left">{columnsLabels["project__name"]}</div>,
|
||||
cell: ({ row }) => {
|
||||
const project = getProjectById(row.original.project_id);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{project?.logo_props ? (
|
||||
<Logo logo={project.logo_props} size={18} />
|
||||
) : (
|
||||
<Briefcase className="h-4 w-4" />
|
||||
)}
|
||||
{project?.name}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
: {
|
||||
accessorKey: "display_name",
|
||||
header: () => <div className="text-left">{columnsLabels["display_name"]}</div>,
|
||||
cell: ({ row }: { row: Row<WorkItemInsightColumns> }) => (
|
||||
<div className="text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
{row.original.avatar_url && row.original.avatar_url !== "" ? (
|
||||
<Avatar
|
||||
name={row.original.display_name}
|
||||
src={getFileURL(row.original.avatar_url)}
|
||||
size={24}
|
||||
shape="circle"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-custom-background-80 capitalize overflow-hidden">
|
||||
{row.original.display_name ? (
|
||||
row.original.display_name?.[0]
|
||||
) : (
|
||||
<UserRound className="text-custom-text-200 " size={12} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span className="break-words text-custom-text-200">
|
||||
{row.original.display_name ?? t(`Unassigned`)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "backlog_work_items",
|
||||
header: () => <div className="text-right">{columnsLabels["backlog_work_items"]}</div>,
|
||||
|
|
@ -85,7 +128,7 @@ const WorkItemsInsightTable = observer(() => {
|
|||
cell: ({ row }) => <div className="text-right">{row.original.cancelled_work_items}</div>,
|
||||
},
|
||||
] as ColumnDef<AnalyticsTableDataMap["work-items"]>[],
|
||||
[getProjectById]
|
||||
[columnsLabels, getProjectById, isPeekView, t]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
107
web/core/components/analytics/old-page.tsx
Normal file
107
web/core/components/analytics/old-page.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
"use client";
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
// components
|
||||
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const OldAnalyticsPage = observer(() => {
|
||||
const searchParams = useSearchParams();
|
||||
const analytics_tab = searchParams.get("analytics_tab");
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, loader } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
|
||||
: undefined;
|
||||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
// TODO: refactor loader implementation
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
{workspaceProjectIds && (
|
||||
<>
|
||||
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
|
||||
<div className="flex h-full flex-col overflow-hidden bg-custom-background-100">
|
||||
<Tab.Group as={Fragment} defaultIndex={analytics_tab === "custom" ? 1 : 0}>
|
||||
<Header variant={EHeaderVariant.SECONDARY}>
|
||||
<Tab.List as="div" className="flex space-x-2 h-full">
|
||||
{ANALYTICS_TABS.map((tab) => (
|
||||
<Tab key={tab.key} as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
className={`text-sm group relative flex items-center gap-1 h-full px-3 cursor-pointer transition-all font-medium outline-none ${
|
||||
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
{t(tab.i18n_title)}
|
||||
<div
|
||||
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
</Header>
|
||||
<Tab.Panels as={Fragment}>
|
||||
<Tab.Panel as={Fragment}>
|
||||
<ScopeAndDemand fullScreen />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel as={Fragment}>
|
||||
<CustomAnalytics fullScreen />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
) : (
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_analytics.empty_state.general.title")}
|
||||
description={t("workspace_analytics.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_analytics.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default OldAnalyticsPage;
|
||||
|
|
@ -10,6 +10,9 @@ export interface IAnalyticsStoreV2 {
|
|||
currentTab: TAnalyticsTabsV2Base;
|
||||
selectedProjects: string[];
|
||||
selectedDuration: DurationType;
|
||||
selectedCycle: string;
|
||||
selectedModule: string;
|
||||
isPeekView?: boolean;
|
||||
|
||||
//computed
|
||||
selectedDurationLabel: DurationType | null;
|
||||
|
|
@ -17,25 +20,36 @@ export interface IAnalyticsStoreV2 {
|
|||
//actions
|
||||
updateSelectedProjects: (projects: string[]) => void;
|
||||
updateSelectedDuration: (duration: DurationType) => void;
|
||||
updateSelectedCycle: (cycle: string) => void;
|
||||
updateSelectedModule: (module: string) => void;
|
||||
updateIsPeekView: (isPeekView: boolean) => void;
|
||||
}
|
||||
|
||||
export class AnalyticsStoreV2 implements IAnalyticsStoreV2 {
|
||||
//observables
|
||||
currentTab: TAnalyticsTabsV2Base = "overview";
|
||||
selectedProjects: DurationType[] = [];
|
||||
selectedProjects: string[] = [];
|
||||
selectedDuration: DurationType = "last_30_days";
|
||||
|
||||
constructor(_rootStore: CoreRootStore) {
|
||||
selectedCycle: string = "";
|
||||
selectedModule: string = "";
|
||||
isPeekView: boolean = false;
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
currentTab: observable.ref,
|
||||
selectedDuration: observable.ref,
|
||||
selectedProjects: observable.ref,
|
||||
selectedCycle: observable.ref,
|
||||
selectedModule: observable.ref,
|
||||
isPeekView: observable.ref,
|
||||
// computed
|
||||
selectedDurationLabel: computed,
|
||||
// actions
|
||||
updateSelectedProjects: action,
|
||||
updateSelectedDuration: action,
|
||||
updateSelectedCycle: action,
|
||||
updateSelectedModule: action,
|
||||
updateIsPeekView: action,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +58,6 @@ export class AnalyticsStoreV2 implements IAnalyticsStoreV2 {
|
|||
}
|
||||
|
||||
updateSelectedProjects = (projects: string[]) => {
|
||||
const initialState = this.selectedProjects;
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.selectedProjects = projects;
|
||||
|
|
@ -65,4 +78,22 @@ export class AnalyticsStoreV2 implements IAnalyticsStoreV2 {
|
|||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
updateSelectedCycle = (cycle: string) => {
|
||||
runInAction(() => {
|
||||
this.selectedCycle = cycle;
|
||||
});
|
||||
};
|
||||
|
||||
updateSelectedModule = (module: string) => {
|
||||
runInAction(() => {
|
||||
this.selectedModule = module;
|
||||
});
|
||||
};
|
||||
|
||||
updateIsPeekView = (isPeekView: boolean) => {
|
||||
runInAction(() => {
|
||||
this.isPeekView = isPeekView;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export class CoreRootStore {
|
|||
this.transient = new TransientStore();
|
||||
this.stickyStore = new StickyStore();
|
||||
this.editorAssetStore = new EditorAssetStore();
|
||||
this.analyticsV2 = new AnalyticsStoreV2(this);
|
||||
this.analyticsV2 = new AnalyticsStoreV2();
|
||||
}
|
||||
|
||||
resetOnSignOut() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue