mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
ae07df6061
Add a team document-usage dashboard at /t/:teamUrl/analytics for team admins and managers, behind the NEXT_PUBLIC_FEATURE_TEAM_ANALYTICS_ENABLED rollout flag (enabled by default, set to "false" to gate it off). Backend: - getTeamAnalytics Kysely query over team-produced documents across all folders, with exact COUNT(*) (no STATS_COUNT_CAP). Each metric uses its own date axis: Sent/Draft/Pending by createdAt, Completed by Envelope.completedAt, Declined by the DOCUMENT_RECIPIENT_REJECTED audit-log timestamp. - resolveAnalyticsPeriod turns calendar presets into half-open [start, end) ranges in the viewer's timezone, falling back to UTC. - team.getAnalytics tRPC route gated to ADMIN/MANAGER. Frontend: - Standalone /t/:teamUrl/analytics route whose loader gates the flag and role, silently redirecting members to documents. - Headline metrics and compact stat tiles, a member multiselect filter, a calendar-preset period selector, and an empty state. - Role- and flag-gated nav entries in the desktop and mobile navigation. Tests: - Unit tests for the period resolver (timezone and preset boundaries). - Integration/E2E tests for the query semantics (date axes, audit-log decline, all-folders aggregation, sender attribution), access control, filters and the empty state.
146 lines
4.5 KiB
TypeScript
146 lines
4.5 KiB
TypeScript
import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification';
|
|
import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email';
|
|
import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification';
|
|
import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email';
|
|
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
|
|
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
|
|
|
|
import { authenticatedProcedure, router } from '../trpc';
|
|
import { createTeamRoute } from './create-team';
|
|
import { createTeamGroupsRoute } from './create-team-groups';
|
|
import { createTeamMembersRoute } from './create-team-members';
|
|
import { deleteTeamRoute } from './delete-team';
|
|
import { deleteTeamGroupRoute } from './delete-team-group';
|
|
import { deleteTeamMemberRoute } from './delete-team-member';
|
|
import { findTeamGroupsRoute } from './find-team-groups';
|
|
import { findTeamMembersRoute } from './find-team-members';
|
|
import { findTeamsRoute } from './find-teams';
|
|
import { getTeamRoute } from './get-team';
|
|
import { getTeamAnalyticsRoute } from './get-team-analytics';
|
|
import { getTeamMembersRoute } from './get-team-members';
|
|
import {
|
|
ZCreateTeamEmailVerificationMutationSchema,
|
|
ZDeleteTeamEmailMutationSchema,
|
|
ZDeleteTeamEmailVerificationMutationSchema,
|
|
ZResendTeamEmailVerificationMutationSchema,
|
|
ZUpdateTeamEmailMutationSchema,
|
|
} from './schema';
|
|
import { updateTeamRoute } from './update-team';
|
|
import { updateTeamGroupRoute } from './update-team-group';
|
|
import { updateTeamMemberRoute } from './update-team-member';
|
|
import { updateTeamSettingsRoute } from './update-team-settings';
|
|
|
|
export const teamRouter = router({
|
|
find: findTeamsRoute,
|
|
get: getTeamRoute,
|
|
getAnalytics: getTeamAnalyticsRoute,
|
|
create: createTeamRoute,
|
|
update: updateTeamRoute,
|
|
delete: deleteTeamRoute,
|
|
member: {
|
|
find: findTeamMembersRoute,
|
|
getMany: getTeamMembersRoute,
|
|
createMany: createTeamMembersRoute,
|
|
update: updateTeamMemberRoute,
|
|
delete: deleteTeamMemberRoute,
|
|
},
|
|
group: {
|
|
find: findTeamGroupsRoute,
|
|
createMany: createTeamGroupsRoute,
|
|
update: updateTeamGroupRoute,
|
|
delete: deleteTeamGroupRoute,
|
|
},
|
|
settings: {
|
|
update: updateTeamSettingsRoute,
|
|
},
|
|
|
|
// Old routes (to be migrated)
|
|
// Todo: Refactor into routes.
|
|
email: {
|
|
get: authenticatedProcedure.query(async ({ ctx }) => {
|
|
return await getTeamEmailByEmail({ email: ctx.user.email });
|
|
}),
|
|
update: authenticatedProcedure.input(ZUpdateTeamEmailMutationSchema).mutation(async ({ input, ctx }) => {
|
|
ctx.logger.info({
|
|
input: {
|
|
teamId: input.teamId,
|
|
},
|
|
});
|
|
|
|
return await updateTeamEmail({
|
|
userId: ctx.user.id,
|
|
...input,
|
|
});
|
|
}),
|
|
delete: authenticatedProcedure.input(ZDeleteTeamEmailMutationSchema).mutation(async ({ input, ctx }) => {
|
|
const { teamId } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
teamId,
|
|
},
|
|
});
|
|
|
|
return await deleteTeamEmail({
|
|
userId: ctx.user.id,
|
|
userEmail: ctx.user.email,
|
|
teamId,
|
|
});
|
|
}),
|
|
verification: {
|
|
send: authenticatedProcedure
|
|
.input(ZCreateTeamEmailVerificationMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { teamId, email, name } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
teamId,
|
|
},
|
|
});
|
|
|
|
return await createTeamEmailVerification({
|
|
teamId,
|
|
userId: ctx.user.id,
|
|
data: {
|
|
email,
|
|
name,
|
|
},
|
|
});
|
|
}),
|
|
resend: authenticatedProcedure
|
|
.input(ZResendTeamEmailVerificationMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { teamId } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
teamId,
|
|
},
|
|
});
|
|
|
|
await resendTeamEmailVerification({
|
|
userId: ctx.user.id,
|
|
teamId,
|
|
});
|
|
}),
|
|
delete: authenticatedProcedure
|
|
.input(ZDeleteTeamEmailVerificationMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { teamId } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
teamId,
|
|
},
|
|
});
|
|
|
|
return await deleteTeamEmailVerification({
|
|
userId: ctx.user.id,
|
|
teamId,
|
|
});
|
|
}),
|
|
},
|
|
},
|
|
});
|