plane/apps/app/components/analytics/custom-analytics/sidebar.tsx
Aaryan Khandelwal 1a534a3c19
feat: plane analytics (#1029)
* chore: global bar graph component

* chore: global pie graph component

* chore: global line graph component

* chore: removed unnecessary file

* chore: refactored global chart components to accept all props

* chore: function to convert response to chart data

* chore: global calendar graph component added

* chore: global scatter plot graph component

* feat: analytics boilerplate created

* chore: null value for segment and project

* chore: clean up file

* chore: change project query param key

* chore: export, refresh buttons, analytics table

* fix: analytics fetch key error

* chore: show only integer values in the y-axis

* chore: custom x-axis tick values and bar colors

* refactor: divide analytics page into granular components

* chore: convert analytics page to modal, save analytics modal

* fix: build error

* fix: modal overflow issues, analytics loading screen

* chore: custom tooltip, refactor: graphs folder structure

* refactor: folder structure, chore: x-axis tick values for larger data

* chore: code cleanup

* chore: remove unnecessary files

* fix: refresh analytics button on error

* feat: scope and demand analytics

* refactor: scope and demand and custom analytics folder structure

* fix: dynamic import type

* chore: minor updates

* feat: project, cycle and module level analytics

* style: project analytics modal

* fix: merge conflicts
2023-05-11 17:38:46 +05:30

233 lines
7.7 KiB
TypeScript

import { useRouter } from "next/router";
import { mutate } from "swr";
// react-hook-form
import { Control, Controller, UseFormSetValue } from "react-hook-form";
// services
import analyticsService from "services/analytics.service";
// hooks
import useProjects from "hooks/use-projects";
import useToast from "hooks/use-toast";
// ui
import { CustomMenu, CustomSelect, PrimaryButton } from "components/ui";
// icons
import { ArrowPathIcon, ArrowUpTrayIcon } from "@heroicons/react/24/outline";
// types
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData } from "types";
// fetch-keys
import { ANALYTICS } from "constants/fetch-keys";
// constants
import { ANALYTICS_X_AXIS_VALUES, ANALYTICS_Y_AXIS_VALUES } from "constants/analytics";
type Props = {
analytics: IAnalyticsResponse | undefined;
params: IAnalyticsParams;
control: Control<IAnalyticsParams, any>;
setValue: UseFormSetValue<IAnalyticsParams>;
setSaveAnalyticsModal: React.Dispatch<React.SetStateAction<boolean>>;
fullScreen: boolean;
isProjectLevel?: boolean;
};
export const AnalyticsSidebar: React.FC<Props> = ({
analytics,
params,
control,
setValue,
setSaveAnalyticsModal,
fullScreen,
isProjectLevel = false,
}) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { projects } = useProjects();
const { setToastAlert } = useToast();
const exportAnalytics = () => {
if (!workspaceSlug) return;
const data: IExportAnalyticsFormData = {
x_axis: params.x_axis,
y_axis: params.y_axis,
};
if (params.segment) data.segment = params.segment;
if (params.project) data.project = [params.project];
analyticsService
.exportAnalytics(workspaceSlug.toString(), data)
.then((res) =>
setToastAlert({
type: "success",
title: "Success!",
message: res.message,
})
)
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "There was some error in exporting the analytics. Please try again.",
})
);
};
return (
<div
className={`gap-4 p-5 ${
fullScreen ? "border-l border-brand-base bg-brand-sidebar h-full" : ""
}`}
>
<div className={`sticky top-5 ${fullScreen ? "space-y-4" : "space-y-2"}`}>
<div className="flex items-center justify-between gap-2 flex-shrink-0">
<h5 className="text-lg font-medium">
{analytics?.total ?? 0}{" "}
<span className="text-xs font-normal text-brand-secondary">issues</span>
</h5>
<CustomMenu ellipsis>
<CustomMenu.MenuItem
onClick={() => {
if (!workspaceSlug) return;
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
<div className="flex items-center gap-2">
<ArrowPathIcon className="h-3 w-3" />
Refresh
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={exportAnalytics}>
<div className="flex items-center gap-2">
<ArrowUpTrayIcon className="h-3 w-3" />
Export analytics as CSV
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
<div className={`${fullScreen ? "space-y-4" : "grid items-center gap-4 grid-cols-3"}`}>
{isProjectLevel === false && (
<div>
<h6 className="text-xs text-brand-secondary">Project</h6>
<Controller
name="project"
control={control}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
label={projects.find((p) => p.id === value)?.name ?? "All projects"}
onChange={onChange}
width="w-full"
maxHeight="lg"
>
<CustomSelect.Option value={null}>All projects</CustomSelect.Option>
{projects.map((project) => (
<CustomSelect.Option key={project.id} value={project.id}>
{project.name}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
)}
<div>
<h6 className="text-xs text-brand-secondary">Measure (y-axis)</h6>
<Controller
name="y_axis"
control={control}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
label={
<span>
{ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === value)?.label ?? "None"}
</span>
}
onChange={onChange}
width="w-full"
>
{ANALYTICS_Y_AXIS_VALUES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}>
{item.label}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
<div>
<h6 className="text-xs text-brand-secondary">Dimension (x-axis)</h6>
<Controller
name="x_axis"
control={control}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
label={
<span>{ANALYTICS_X_AXIS_VALUES.find((v) => v.value === value)?.label}</span>
}
onChange={(val: string) => {
if (params.segment === val) setValue("segment", null);
onChange(val);
}}
width="w-full"
maxHeight="lg"
>
{ANALYTICS_X_AXIS_VALUES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}>
{item.label}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
<div>
<h6 className="text-xs text-brand-secondary">Segment</h6>
<Controller
name="segment"
control={control}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
label={
<span>
{ANALYTICS_X_AXIS_VALUES.find((v) => v.value === value)?.label ?? (
<span className="text-brand-secondary">No value</span>
)}
</span>
}
onChange={onChange}
width="w-full"
maxHeight="lg"
>
<CustomSelect.Option value={null}>No value</CustomSelect.Option>
{ANALYTICS_X_AXIS_VALUES.map((item) => {
if (params.x_axis === item.value) return null;
return (
<CustomSelect.Option key={item.value} value={item.value}>
{item.label}
</CustomSelect.Option>
);
})}
</CustomSelect>
)}
/>
</div>
</div>
{/* <div className="flex items-center justify-end gap-2">
<PrimaryButton className="py-1" onClick={() => setSaveAnalyticsModal(true)}>
Save analytics
</PrimaryButton>
</div> */}
</div>
</div>
);
};