mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: add user conversion count to admin panel (#1150)
Displays the count of users who signed up after signing at least one document in the admin panel
This commit is contained in:
@ -5,7 +5,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { BarChart3, FileStack, Settings, User2, Wallet2 } from 'lucide-react';
|
||||
import { BarChart3, FileStack, Settings, Users, Wallet2 } from 'lucide-react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -46,7 +46,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => {
|
||||
asChild
|
||||
>
|
||||
<Link href="/admin/users">
|
||||
<User2 className="mr-2 h-5 w-5" />
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
Users
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@ -2,27 +2,28 @@ import {
|
||||
File,
|
||||
FileCheck,
|
||||
FileClock,
|
||||
FileCog,
|
||||
FileEdit,
|
||||
Mail,
|
||||
MailOpen,
|
||||
PenTool,
|
||||
User as UserIcon,
|
||||
UserPlus2,
|
||||
UserPlus,
|
||||
UserSquare2,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { getDocumentStats } from '@documenso/lib/server-only/admin/get-documents-stats';
|
||||
import { getRecipientsStats } from '@documenso/lib/server-only/admin/get-recipients-stats';
|
||||
import {
|
||||
getUserWithAtLeastOneDocumentPerMonth,
|
||||
getUserWithAtLeastOneDocumentSignedPerMonth,
|
||||
getUserWithSignedDocumentMonthlyGrowth,
|
||||
getUsersCount,
|
||||
getUsersWithSubscriptionsCount,
|
||||
} from '@documenso/lib/server-only/admin/get-users-stats';
|
||||
import { getSignerConversionMonthly } from '@documenso/lib/server-only/user/get-signer-conversion';
|
||||
|
||||
import { CardMetric } from '~/components/(dashboard)/metric-card/metric-card';
|
||||
|
||||
import { SignerConversionChart } from './signer-conversion-chart';
|
||||
import { UserWithDocumentChart } from './user-with-document';
|
||||
|
||||
export default async function AdminStatsPage() {
|
||||
@ -31,16 +32,18 @@ export default async function AdminStatsPage() {
|
||||
usersWithSubscriptionsCount,
|
||||
docStats,
|
||||
recipientStats,
|
||||
userWithAtLeastOneDocumentPerMonth,
|
||||
userWithAtLeastOneDocumentSignedPerMonth,
|
||||
signerConversionMonthly,
|
||||
// userWithAtLeastOneDocumentPerMonth,
|
||||
// userWithAtLeastOneDocumentSignedPerMonth,
|
||||
MONTHLY_USERS_SIGNED,
|
||||
] = await Promise.all([
|
||||
getUsersCount(),
|
||||
getUsersWithSubscriptionsCount(),
|
||||
getDocumentStats(),
|
||||
getRecipientsStats(),
|
||||
getUserWithAtLeastOneDocumentPerMonth(),
|
||||
getUserWithAtLeastOneDocumentSignedPerMonth(),
|
||||
getSignerConversionMonthly(),
|
||||
// getUserWithAtLeastOneDocumentPerMonth(),
|
||||
// getUserWithAtLeastOneDocumentSignedPerMonth(),
|
||||
getUserWithSignedDocumentMonthlyGrowth(),
|
||||
]);
|
||||
|
||||
@ -49,14 +52,15 @@ export default async function AdminStatsPage() {
|
||||
<h2 className="text-4xl font-semibold">Instance Stats</h2>
|
||||
|
||||
<div className="mt-8 grid flex-1 grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<CardMetric icon={UserIcon} title="Total Users" value={usersCount} />
|
||||
<CardMetric icon={Users} title="Total Users" value={usersCount} />
|
||||
<CardMetric icon={File} title="Total Documents" value={docStats.ALL} />
|
||||
<CardMetric
|
||||
icon={UserPlus2}
|
||||
icon={UserPlus}
|
||||
title="Active Subscriptions"
|
||||
value={usersWithSubscriptionsCount}
|
||||
/>
|
||||
<CardMetric icon={UserPlus2} title="App Version" value={`v${process.env.APP_VERSION}`} />
|
||||
|
||||
<CardMetric icon={FileCog} title="App Version" value={`v${process.env.APP_VERSION}`} />
|
||||
</div>
|
||||
|
||||
<div className="mt-16 gap-8">
|
||||
@ -88,7 +92,7 @@ export default async function AdminStatsPage() {
|
||||
|
||||
<div className="mt-16">
|
||||
<h3 className="text-3xl font-semibold">Charts</h3>
|
||||
<div className="mt-5 grid grid-cols-2 gap-10">
|
||||
<div className="mt-5 grid grid-cols-2 gap-8">
|
||||
<UserWithDocumentChart
|
||||
data={MONTHLY_USERS_SIGNED}
|
||||
title="MAU (created document)"
|
||||
@ -100,6 +104,12 @@ export default async function AdminStatsPage() {
|
||||
title="MAU (had document completed)"
|
||||
tooltip="Monthly Active Users: Users that had at least one of their documents completed"
|
||||
/>
|
||||
<SignerConversionChart title="Signers that Signed Up" data={signerConversionMonthly} />
|
||||
<SignerConversionChart
|
||||
title="Total Signers that Signed Up"
|
||||
data={signerConversionMonthly}
|
||||
cummulative
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { GetSignerConversionMonthlyResult } from '@documenso/lib/server-only/user/get-signer-conversion';
|
||||
|
||||
export type SignerConversionChartProps = {
|
||||
className?: string;
|
||||
title: string;
|
||||
cummulative?: boolean;
|
||||
data: GetSignerConversionMonthlyResult;
|
||||
};
|
||||
|
||||
export const SignerConversionChart = ({
|
||||
className,
|
||||
data,
|
||||
title,
|
||||
cummulative = false,
|
||||
}: SignerConversionChartProps) => {
|
||||
const formattedData = [...data].reverse().map(({ month, count, cume_count }) => {
|
||||
return {
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('MMM yyyy'),
|
||||
count: Number(count),
|
||||
signed_count: Number(cume_count),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="border-border flex flex-1 flex-col justify-center rounded-2xl border p-6 pl-2">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
</div>
|
||||
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
<Tooltip
|
||||
labelStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value, name) => [
|
||||
Number(value).toLocaleString('en-US'),
|
||||
name === 'Recipients',
|
||||
]}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
|
||||
<Bar
|
||||
dataKey={cummulative ? 'signed_count' : 'count'}
|
||||
fill="hsl(var(--primary))"
|
||||
radius={[4, 4, 0, 0]}
|
||||
maxBarSize={60}
|
||||
label="Recipients"
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -68,7 +68,7 @@ export const UserWithDocumentChart = ({
|
||||
</div>
|
||||
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart className="bg-white" data={formattedData(data, completed)}>
|
||||
<BarChart data={formattedData(data, completed)}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricPr
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h3 className="text-primary-forground flex items-end text-sm font-medium leading-tight">
|
||||
<h3 className="text-primary-forground mb-2 flex items-end text-sm font-medium leading-tight">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user