forked from github/plane
fix: workspace and analytics page casing
This commit is contained in:
parent
3400c119bc
commit
aae54fb69f
@ -15,7 +15,7 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
||||||
<h4 className="font-medium">Selected Projects</h4>
|
<h4 className="font-medium">Selected projects</h4>
|
||||||
<div className="space-y-6 mt-4 h-full overflow-y-auto">
|
<div className="space-y-6 mt-4 h-full overflow-y-auto">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<div key={project.id} className="w-full">
|
<div key={project.id} className="w-full">
|
||||||
|
@ -29,172 +29,172 @@ type Props = {
|
|||||||
|
|
||||||
const analyticsService = new AnalyticsService();
|
const analyticsService = new AnalyticsService();
|
||||||
|
|
||||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
({ analytics, params, fullScreen, isProjectLevel = false }) => {
|
const { analytics, params, fullScreen, isProjectLevel = false } = props;
|
||||||
const router = useRouter();
|
// router
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
// toast
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
// mobx store
|
||||||
|
const {
|
||||||
|
project: { workspaceProjects, getProjectById },
|
||||||
|
cycle: { getCycleById, fetchCycleWithId },
|
||||||
|
module: { getModuleById, fetchModuleDetails },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const projectDetails =
|
||||||
|
workspaceSlug && projectId
|
||||||
|
? getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const { user: userStore, project: projectStore, cycle: cycleStore, module: moduleStore } = useMobxStore();
|
const trackExportAnalytics = () => {
|
||||||
|
const eventPayload: any = {
|
||||||
const user = userStore.currentUser;
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
|
params: {
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
|
||||||
const projectDetails =
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const trackExportAnalytics = () => {
|
|
||||||
if (!user) return;
|
|
||||||
|
|
||||||
const eventPayload: any = {
|
|
||||||
workspaceSlug: workspaceSlug?.toString(),
|
|
||||||
params: {
|
|
||||||
x_axis: params.x_axis,
|
|
||||||
y_axis: params.y_axis,
|
|
||||||
group: params.segment,
|
|
||||||
project: params.project,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (projectDetails) {
|
|
||||||
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
|
||||||
|
|
||||||
eventPayload.workspaceId = workspaceDetails.id;
|
|
||||||
eventPayload.workspaceName = workspaceDetails.name;
|
|
||||||
eventPayload.projectId = projectDetails.id;
|
|
||||||
eventPayload.projectIdentifier = projectDetails.identifier;
|
|
||||||
eventPayload.projectName = projectDetails.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleDetails || moduleDetails) {
|
|
||||||
const details = cycleDetails || moduleDetails;
|
|
||||||
|
|
||||||
eventPayload.workspaceId = details?.workspace_detail?.id;
|
|
||||||
eventPayload.workspaceName = details?.workspace_detail?.name;
|
|
||||||
eventPayload.projectId = details?.project_detail.id;
|
|
||||||
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
|
||||||
eventPayload.projectName = details?.project_detail.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleDetails) {
|
|
||||||
eventPayload.cycleId = cycleDetails.id;
|
|
||||||
eventPayload.cycleName = cycleDetails.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleDetails) {
|
|
||||||
eventPayload.moduleId = moduleDetails.id;
|
|
||||||
eventPayload.moduleName = moduleDetails.name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportAnalytics = () => {
|
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
const data: IExportAnalyticsFormData = {
|
|
||||||
x_axis: params.x_axis,
|
x_axis: params.x_axis,
|
||||||
y_axis: params.y_axis,
|
y_axis: params.y_axis,
|
||||||
};
|
group: params.segment,
|
||||||
|
project: params.project,
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
trackExportAnalytics();
|
|
||||||
})
|
|
||||||
.catch(() =>
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "There was some error in exporting the analytics. Please try again.",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
if (projectDetails) {
|
||||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||||
|
|
||||||
// fetch cycle details
|
eventPayload.workspaceId = workspaceDetails.id;
|
||||||
useEffect(() => {
|
eventPayload.workspaceName = workspaceDetails.name;
|
||||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
eventPayload.projectId = projectDetails.id;
|
||||||
|
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||||
|
eventPayload.projectName = projectDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
cycleStore.fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
if (cycleDetails || moduleDetails) {
|
||||||
}, [cycleId, cycleDetails, cycleStore, projectId, workspaceSlug]);
|
const details = cycleDetails || moduleDetails;
|
||||||
|
|
||||||
// fetch module details
|
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||||
useEffect(() => {
|
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
eventPayload.projectId = details?.project_detail.id;
|
||||||
|
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||||
|
eventPayload.projectName = details?.project_detail.name;
|
||||||
|
}
|
||||||
|
|
||||||
moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
if (cycleDetails) {
|
||||||
}, [moduleId, moduleDetails, moduleStore, projectId, workspaceSlug]);
|
eventPayload.cycleId = cycleDetails.id;
|
||||||
|
eventPayload.cycleName = cycleDetails.name;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
if (moduleDetails) {
|
||||||
|
eventPayload.moduleId = moduleDetails.id;
|
||||||
|
eventPayload.moduleName = moduleDetails.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const exportAnalytics = () => {
|
||||||
<div
|
if (!workspaceSlug) return;
|
||||||
className={`px-5 py-2.5 flex items-center justify-between space-y-2 ${
|
|
||||||
fullScreen
|
const data: IExportAnalyticsFormData = {
|
||||||
? "border-l border-custom-border-200 md:h-full md:border-l md:border-custom-border-200 md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
|
x_axis: params.x_axis,
|
||||||
: ""
|
y_axis: params.y_axis,
|
||||||
}`}
|
};
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
trackExportAnalytics();
|
||||||
|
})
|
||||||
|
.catch(() =>
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "There was some error in exporting the analytics. Please try again.",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
|
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||||
|
|
||||||
|
// fetch cycle details
|
||||||
|
useEffect(() => {
|
||||||
|
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||||
|
|
||||||
|
fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||||
|
}, [cycleId, cycleDetails, fetchCycleWithId, projectId, workspaceSlug]);
|
||||||
|
|
||||||
|
// fetch module details
|
||||||
|
useEffect(() => {
|
||||||
|
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||||
|
|
||||||
|
fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||||
|
}, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]);
|
||||||
|
|
||||||
|
const selectedProjects =
|
||||||
|
params.project && params.project.length > 0 ? params.project : workspaceProjects?.map((p) => p.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`px-5 py-2.5 flex items-center justify-between space-y-2 ${
|
||||||
|
fullScreen
|
||||||
|
? "border-l border-custom-border-200 md:h-full md:border-l md:border-custom-border-200 md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
|
||||||
|
<LayersIcon height={14} width={14} />
|
||||||
|
{analytics ? analytics.total : "..."} Issues
|
||||||
|
</div>
|
||||||
|
{isProjectLevel && (
|
||||||
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
|
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
|
||||||
<LayersIcon height={14} width={14} />
|
<CalendarDays className="h-3.5 w-3.5" />
|
||||||
{analytics ? analytics.total : "..."} Issues
|
{renderShortDate(
|
||||||
|
(cycleId
|
||||||
|
? cycleDetails?.created_at
|
||||||
|
: moduleId
|
||||||
|
? moduleDetails?.created_at
|
||||||
|
: projectDetails?.created_at) ?? ""
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isProjectLevel && (
|
)}
|
||||||
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
|
|
||||||
<CalendarDays className="h-3.5 w-3.5" />
|
|
||||||
{renderShortDate(
|
|
||||||
(cycleId
|
|
||||||
? cycleDetails?.created_at
|
|
||||||
: moduleId
|
|
||||||
? moduleDetails?.created_at
|
|
||||||
: projectDetails?.created_at) ?? ""
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="h-full w-full overflow-hidden">
|
|
||||||
{fullScreen ? (
|
|
||||||
<>
|
|
||||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
|
||||||
<CustomAnalyticsSidebarProjectsList
|
|
||||||
projects={projects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<CustomAnalyticsSidebarHeader />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 flex-wrap justify-self-end">
|
|
||||||
<Button
|
|
||||||
variant="neutral-primary"
|
|
||||||
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
|
||||||
onClick={() => {
|
|
||||||
if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
|
||||||
Export as CSV
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="h-full w-full overflow-hidden">
|
||||||
}
|
{fullScreen ? (
|
||||||
);
|
<>
|
||||||
|
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||||
|
<CustomAnalyticsSidebarProjectsList
|
||||||
|
projects={workspaceProjects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<CustomAnalyticsSidebarHeader />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-wrap justify-self-end">
|
||||||
|
<Button
|
||||||
|
variant="neutral-primary"
|
||||||
|
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||||
|
onClick={() => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
||||||
|
Export as CSV
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -53,7 +53,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
|
|||||||
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
|
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
|
||||||
<p className="flex items-center gap-1 text-custom-text-200">
|
<p className="flex items-center gap-1 text-custom-text-200">
|
||||||
<Triangle className="h-4 w-4" />
|
<Triangle className="h-4 w-4" />
|
||||||
<span>Estimate Demand:</span>
|
<span>Estimate demand:</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum}
|
{defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum}
|
||||||
|
@ -17,45 +17,49 @@ type Props = {
|
|||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AnalyticsLeaderBoard: React.FC<Props> = ({ users, title, emptyStateMessage, workspaceSlug }) => (
|
export const AnalyticsLeaderBoard: React.FC<Props> = (props) => {
|
||||||
<div className="p-3 border border-custom-border-200 rounded-[10px]">
|
const { users, title, emptyStateMessage, workspaceSlug } = props;
|
||||||
<h6 className="text-base font-medium">{title}</h6>
|
|
||||||
{users.length > 0 ? (
|
return (
|
||||||
<div className="mt-3 space-y-3">
|
<div className="p-3 border border-custom-border-200 rounded-[10px]">
|
||||||
{users.map((user) => (
|
<h6 className="text-base font-medium">{title}</h6>
|
||||||
<a
|
{users.length > 0 ? (
|
||||||
key={user.display_name ?? "None"}
|
<div className="mt-3 space-y-3">
|
||||||
href={`/${workspaceSlug}/profile/${user.id}`}
|
{users.map((user) => (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
key={user.display_name ?? "None"}
|
||||||
className="flex items-start justify-between gap-4 text-xs"
|
href={`/${workspaceSlug}/profile/${user.id}`}
|
||||||
>
|
target="_blank"
|
||||||
<div className="flex items-center gap-2">
|
rel="noopener noreferrer"
|
||||||
{user && user.avatar && user.avatar !== "" ? (
|
className="flex items-start justify-between gap-4 text-xs"
|
||||||
<div className="relative rounded-full h-4 w-4 flex-shrink-0">
|
>
|
||||||
<img
|
<div className="flex items-center gap-2">
|
||||||
src={user.avatar}
|
{user && user.avatar && user.avatar !== "" ? (
|
||||||
className="absolute top-0 left-0 h-full w-full object-cover rounded-full"
|
<div className="relative rounded-full h-4 w-4 flex-shrink-0">
|
||||||
alt={user.display_name ?? "None"}
|
<img
|
||||||
/>
|
src={user.avatar}
|
||||||
</div>
|
className="absolute top-0 left-0 h-full w-full object-cover rounded-full"
|
||||||
) : (
|
alt={user.display_name ?? "None"}
|
||||||
<div className="grid place-items-center flex-shrink-0 rounded-full bg-gray-700 text-[11px] capitalize text-white h-4 w-4">
|
/>
|
||||||
{user.display_name !== "" ? user?.display_name?.[0] : "?"}
|
</div>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div className="grid place-items-center flex-shrink-0 rounded-full bg-gray-700 text-[11px] capitalize text-white h-4 w-4">
|
||||||
<span className="break-words text-custom-text-200">
|
{user.display_name !== "" ? user?.display_name?.[0] : "?"}
|
||||||
{user.display_name !== "" ? `${user.display_name}` : "No assignee"}
|
</div>
|
||||||
</span>
|
)}
|
||||||
</div>
|
<span className="break-words text-custom-text-200">
|
||||||
<span className="flex-shrink-0">{user.count}</span>
|
{user.display_name !== "" ? `${user.display_name}` : "No assignee"}
|
||||||
</a>
|
</span>
|
||||||
))}
|
</div>
|
||||||
</div>
|
<span className="flex-shrink-0">{user.count}</span>
|
||||||
) : (
|
</a>
|
||||||
<div className="px-7 py-4">
|
))}
|
||||||
<ProfileEmptyState title="No Data yet" description={emptyStateMessage} image={emptyUsers} />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div className="px-7 py-4">
|
||||||
</div>
|
<ProfileEmptyState title="No data yet" description={emptyStateMessage} image={emptyUsers} />
|
||||||
);
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -2,17 +2,16 @@ import { useState } from "react";
|
|||||||
import { LayoutGrid, Zap } from "lucide-react";
|
import { LayoutGrid, Zap } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// images
|
|
||||||
import githubBlackImage from "/public/logos/github-black.png";
|
|
||||||
import githubWhiteImage from "/public/logos/github-white.png";
|
|
||||||
// components
|
// components
|
||||||
import { ProductUpdatesModal } from "components/common";
|
import { ProductUpdatesModal } from "components/common";
|
||||||
|
// ui
|
||||||
import { Breadcrumbs } from "@plane/ui";
|
import { Breadcrumbs } from "@plane/ui";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
// assetsˀ
|
||||||
|
import githubBlackImage from "/public/logos/github-black.png";
|
||||||
|
import githubWhiteImage from "/public/logos/github-white.png";
|
||||||
|
|
||||||
export const WorkspaceDashboardHeader = () => {
|
export const WorkspaceDashboardHeader = () => {
|
||||||
const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
|
const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
|
||||||
const { trackEvent: { postHogEventTracker } } = useMobxStore();
|
|
||||||
// theme
|
// theme
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
|
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
|
||||||
>
|
>
|
||||||
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
|
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
|
||||||
{"What's New?"}
|
What{"'"}s new?
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
|
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@ -9,9 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { TourRoot } from "components/onboarding";
|
import { TourRoot } from "components/onboarding";
|
||||||
import { UserGreetingsView } from "components/user";
|
import { UserGreetingsView } from "components/user";
|
||||||
import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace";
|
import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace";
|
||||||
import { Button } from "@plane/ui";
|
|
||||||
// images
|
// images
|
||||||
import emptyDashboard from "public/empty-state/dashboard.svg";
|
|
||||||
import { NewEmptyState } from "components/common/new-empty-state";
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
import emptyProject from "public/empty-state/dashboard_empty_project.webp";
|
import emptyProject from "public/empty-state/dashboard_empty_project.webp";
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@ export const IssuesList: React.FC<Props> = ({ issues, type }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-semibold capitalize">{type} Issues</h3>
|
<h3 className="mb-2 font-semibold">
|
||||||
|
<span className="capitalize">{type}</span> issues
|
||||||
|
</h3>
|
||||||
{issues ? (
|
{issues ? (
|
||||||
<div className="h-[calc(100%-2.25rem)] rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 text-sm">
|
<div className="h-[calc(100%-2.25rem)] rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 text-sm">
|
||||||
<div
|
<div
|
||||||
@ -44,7 +46,7 @@ export const IssuesList: React.FC<Props> = ({ issues, type }) => {
|
|||||||
>
|
>
|
||||||
<h4 className="capitalize">{type}</h4>
|
<h4 className="capitalize">{type}</h4>
|
||||||
<h4 className="col-span-2">Issue</h4>
|
<h4 className="col-span-2">Issue</h4>
|
||||||
<h4>{type === "overdue" ? "Due" : "Start"} Date</h4>
|
<h4>{type === "overdue" ? "Due" : "Start"} date</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-72 overflow-y-scroll">
|
<div className="max-h-72 overflow-y-scroll">
|
||||||
{issues.length > 0 ? (
|
{issues.length > 0 ? (
|
||||||
|
@ -11,7 +11,7 @@ type Props = {
|
|||||||
|
|
||||||
export const IssuesPieChart: React.FC<Props> = ({ groupedIssues }) => (
|
export const IssuesPieChart: React.FC<Props> = ({ groupedIssues }) => (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-semibold">Issues by States</h3>
|
<h3 className="mb-2 font-semibold">Issues by states</h3>
|
||||||
<div className="rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4">
|
<div className="rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-4">
|
<div className="grid grid-cols-1 sm:grid-cols-4">
|
||||||
<div className="sm:col-span-3">
|
<div className="sm:col-span-3">
|
||||||
|
@ -15,6 +15,7 @@ type Props = {
|
|||||||
export const IssuesStats: React.FC<Props> = ({ data }) => {
|
export const IssuesStats: React.FC<Props> = ({ data }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
|
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
|
||||||
<div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0">
|
<div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0">
|
||||||
@ -77,8 +78,8 @@ export const IssuesStats: React.FC<Props> = ({ data }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 lg:col-span-2">
|
<div className="p-4 lg:col-span-2">
|
||||||
<h3 className="mb-2 font-semibold capitalize flex items-center gap-2">
|
<h3 className="mb-2 font-semibold flex items-center gap-2">
|
||||||
Activity Graph
|
Activity graph
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
|
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
|
||||||
className="w-72 border border-custom-border-200"
|
className="w-72 border border-custom-border-200"
|
||||||
|
@ -60,7 +60,7 @@ export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
|
|||||||
export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] = [
|
export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] = [
|
||||||
{
|
{
|
||||||
value: "issue_count",
|
value: "issue_count",
|
||||||
label: "Issue Count",
|
label: "Issue count",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "estimate",
|
value: "estimate",
|
||||||
|
@ -17,7 +17,6 @@ import emptyAnalytics from "public/empty-state/analytics.svg";
|
|||||||
import { ANALYTICS_TABS } from "constants/analytics";
|
import { ANALYTICS_TABS } from "constants/analytics";
|
||||||
// type
|
// type
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
import { NewEmptyState } from "components/common/new-empty-state";
|
|
||||||
|
|
||||||
const AnalyticsPage: NextPageWithLayout = observer(() => {
|
const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
Loading…
Reference in New Issue
Block a user