feat: team analytics

This commit is contained in:
Ephraim Atta-Duncan
2025-08-20 15:19:09 +00:00
parent a51110d276
commit 202702b1c7
16 changed files with 834 additions and 5 deletions

View File

@ -0,0 +1,55 @@
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
import { getUserDocumentStats, getUserRecipientsStats } from '@documenso/lib/server-only/analytics';
import {
type DateFilterPeriod,
getDateRangeFromPeriod,
} from '~/components/analytics/analytics-date-filter';
import { AnalyticsPage } from '~/components/analytics/analytics-page';
import { useAnalyticsFilter } from '~/hooks/use-analytics-filter';
import { appMetaTags } from '~/utils/meta';
import type { Route } from './+types/analytics';
export function meta() {
return appMetaTags('Personal Analytics');
}
export async function loader({ request }: Route.LoaderArgs) {
const session = await getSession(request);
const url = new URL(request.url);
const period = (url.searchParams.get('period') as DateFilterPeriod) || 'all';
const { dateFrom, dateTo } = getDateRangeFromPeriod(period);
const [docStats, recipientStats] = await Promise.all([
getUserDocumentStats({ userId: session.user.id, dateFrom, dateTo }),
getUserRecipientsStats({ userId: session.user.id, dateFrom, dateTo }),
]);
return {
docStats,
recipientStats,
period,
};
}
export default function PersonalAnalyticsPage({ loaderData }: Route.ComponentProps) {
const { handlePeriodChange } = useAnalyticsFilter();
const { docStats, recipientStats, period } = loaderData;
return (
<AnalyticsPage
title="Personal Analytics"
subtitle="Your personal document signing analytics and insights"
data={{
docStats,
recipientStats,
period,
}}
showCharts={false}
onPeriodChange={handlePeriodChange}
containerClassName=""
/>
);
}

View File

@ -0,0 +1,88 @@
import { redirect } from 'react-router';
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
import {
getTeamDocumentStats,
getTeamMonthlyActiveUsers,
getTeamRecipientsStats,
} from '@documenso/lib/server-only/analytics';
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
import {
type DateFilterPeriod,
getDateRangeFromPeriod,
} from '~/components/analytics/analytics-date-filter';
import { AnalyticsPage } from '~/components/analytics/analytics-page';
import { useAnalyticsFilter } from '~/hooks/use-analytics-filter';
import { useCurrentTeam } from '~/providers/team';
import { appMetaTags } from '~/utils/meta';
import type { Route } from './+types/analytics';
export function meta() {
return appMetaTags('Team Analytics');
}
export async function loader({ request, params }: Route.LoaderArgs) {
try {
const session = await getSession(request);
const url = new URL(request.url);
const period = (url.searchParams.get('period') as DateFilterPeriod) || 'all';
const team = await getTeamByUrl({
userId: session.user.id,
teamUrl: params.teamUrl,
});
if (!team || !canExecuteTeamAction('MANAGE_TEAM', team.currentTeamRole)) {
throw redirect(`/t/${params.teamUrl}`);
}
const { dateFrom, dateTo } = getDateRangeFromPeriod(period);
const [docStats, recipientStats, monthlyActiveUsers] = await Promise.all([
getTeamDocumentStats({ teamId: team.id, dateFrom, dateTo }),
getTeamRecipientsStats({ teamId: team.id, dateFrom, dateTo }),
getTeamMonthlyActiveUsers(team.id),
]);
return {
docStats,
recipientStats,
monthlyActiveUsers,
period,
};
} catch (error) {
console.error('Failed to load team analytics:', error);
// If it's a redirect, re-throw it
if (error instanceof Response) {
throw error;
}
throw new Response('Failed to load team analytics data', { status: 500 });
}
}
export default function TeamAnalyticsPage({ loaderData }: Route.ComponentProps) {
const team = useCurrentTeam();
const { handlePeriodChange } = useAnalyticsFilter();
const { docStats, recipientStats, monthlyActiveUsers, period } = loaderData;
return (
<AnalyticsPage
title="Team Analytics"
subtitle={`Analytics and insights for ${team.name}`}
data={{
docStats,
recipientStats,
monthlyData: monthlyActiveUsers,
period,
}}
showCharts={true}
onPeriodChange={handlePeriodChange}
/>
);
}