mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
Compare commits
1 Commits
perf/insig
...
fix/improv
| Author | SHA1 | Date | |
|---|---|---|---|
| d4e259a9a7 |
@ -202,8 +202,12 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
|||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="mt-8 flex w-full max-w-sm items-center justify-center gap-4">
|
<div className="mt-8 flex w-full max-w-xs flex-col items-stretch gap-4 md:w-auto md:max-w-none md:flex-row md:items-center">
|
||||||
<DocumentShareButton documentId={document.id} token={recipient.token} />
|
<DocumentShareButton
|
||||||
|
documentId={document.id}
|
||||||
|
token={recipient.token}
|
||||||
|
className="w-full max-w-none md:flex-1"
|
||||||
|
/>
|
||||||
|
|
||||||
{isDocumentCompleted(document.status) && (
|
{isDocumentCompleted(document.status) && (
|
||||||
<EnvelopeDownloadDialog
|
<EnvelopeDownloadDialog
|
||||||
@ -212,13 +216,21 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
|||||||
envelopeItems={document.envelopeItems}
|
envelopeItems={document.envelopeItems}
|
||||||
token={recipient?.token}
|
token={recipient?.token}
|
||||||
trigger={
|
trigger={
|
||||||
<Button type="button" variant="outline" className="flex-1">
|
<Button type="button" variant="outline" className="flex-1 md:flex-initial">
|
||||||
<DownloadIcon className="mr-2 h-5 w-5" />
|
<DownloadIcon className="mr-2 h-5 w-5" />
|
||||||
<Trans>Download</Trans>
|
<Trans>Download</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{user && (
|
||||||
|
<Button asChild>
|
||||||
|
<Link to="/">
|
||||||
|
<Trans>Go Back Home</Trans>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -238,12 +250,6 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
|||||||
<ClaimAccount defaultName={recipientName} defaultEmail={recipient.email} />
|
<ClaimAccount defaultName={recipientName} defaultEmail={recipient.email} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{user && (
|
|
||||||
<Link to="/" className="text-documenso-700 hover:text-documenso-600 mt-2">
|
|
||||||
<Trans>Go Back Home</Trans>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -106,5 +106,5 @@
|
|||||||
"vite-plugin-babel-macros": "^1.0.6",
|
"vite-plugin-babel-macros": "^1.0.6",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
},
|
},
|
||||||
"version": "2.0.13"
|
"version": "2.0.12"
|
||||||
}
|
}
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "2.0.13",
|
"version": "2.0.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "2.0.13",
|
"version": "2.0.12",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -101,7 +101,7 @@
|
|||||||
},
|
},
|
||||||
"apps/remix": {
|
"apps/remix": {
|
||||||
"name": "@documenso/remix",
|
"name": "@documenso/remix",
|
||||||
"version": "2.0.13",
|
"version": "2.0.12",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cantoo/pdf-lib": "^2.5.2",
|
"@cantoo/pdf-lib": "^2.5.2",
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.13",
|
"version": "2.0.12",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"dev": "turbo run dev --filter=@documenso/remix",
|
"dev": "turbo run dev --filter=@documenso/remix",
|
||||||
|
|||||||
@ -116,28 +116,28 @@ async function getTeamInsights(
|
|||||||
): Promise<OrganisationDetailedInsights> {
|
): Promise<OrganisationDetailedInsights> {
|
||||||
const teamsQuery = kyselyPrisma.$kysely
|
const teamsQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Team as t')
|
.selectFrom('Team as t')
|
||||||
|
.leftJoin('Envelope as e', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('t.id', '=', 'e.teamId')
|
||||||
|
.on('e.deletedAt', 'is', null)
|
||||||
|
.on('e.type', '=', sql.lit(EnvelopeType.DOCUMENT)),
|
||||||
|
)
|
||||||
|
.leftJoin('TeamGroup as tg', 'tg.teamId', 't.id')
|
||||||
|
.leftJoin('OrganisationGroup as og', 'og.id', 'tg.organisationGroupId')
|
||||||
|
.leftJoin('OrganisationGroupMember as ogm', 'ogm.groupId', 'og.id')
|
||||||
|
.leftJoin('OrganisationMember as om', 'om.id', 'ogm.organisationMemberId')
|
||||||
.where('t.organisationId', '=', organisationId)
|
.where('t.organisationId', '=', organisationId)
|
||||||
.select((eb) => [
|
.select([
|
||||||
't.id',
|
't.id as id',
|
||||||
't.name',
|
't.name as name',
|
||||||
't.createdAt',
|
't.createdAt as createdAt',
|
||||||
eb
|
sql<number>`COUNT(DISTINCT om."userId")`.as('memberCount'),
|
||||||
.selectFrom('TeamGroup as tg')
|
(createdAtFrom
|
||||||
.innerJoin('OrganisationGroup as og', 'og.id', 'tg.organisationGroupId')
|
? sql<number>`COUNT(DISTINCT CASE WHEN e.id IS NOT NULL AND e."createdAt" >= ${createdAtFrom} THEN e.id END)`
|
||||||
.innerJoin('OrganisationGroupMember as ogm', 'ogm.groupId', 'og.id')
|
: sql<number>`COUNT(DISTINCT e.id)`
|
||||||
.innerJoin('OrganisationMember as om', 'om.id', 'ogm.organisationMemberId')
|
).as('documentCount'),
|
||||||
.whereRef('tg.teamId', '=', 't.id')
|
|
||||||
.select(sql<number>`count(distinct om."userId")`.as('count'))
|
|
||||||
.as('memberCount'),
|
|
||||||
eb
|
|
||||||
.selectFrom('Envelope as e')
|
|
||||||
.whereRef('e.teamId', '=', 't.id')
|
|
||||||
.where('e.deletedAt', 'is', null)
|
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
|
||||||
.$if(!!createdAtFrom, (qb) => qb.where('e.createdAt', '>=', createdAtFrom!))
|
|
||||||
.select(sql<number>`count(e.id)`.as('count'))
|
|
||||||
.as('documentCount'),
|
|
||||||
])
|
])
|
||||||
|
.groupBy(['t.id', 't.name', 't.createdAt'])
|
||||||
.orderBy('documentCount', 'desc')
|
.orderBy('documentCount', 'desc')
|
||||||
.limit(perPage)
|
.limit(perPage)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
@ -164,38 +164,48 @@ async function getUserInsights(
|
|||||||
perPage: number,
|
perPage: number,
|
||||||
createdAtFrom: Date | null,
|
createdAtFrom: Date | null,
|
||||||
): Promise<OrganisationDetailedInsights> {
|
): Promise<OrganisationDetailedInsights> {
|
||||||
const usersQuery = kyselyPrisma.$kysely
|
const usersBase = kyselyPrisma.$kysely
|
||||||
.selectFrom('OrganisationMember as om')
|
.selectFrom('OrganisationMember as om')
|
||||||
.innerJoin('User as u', 'u.id', 'om.userId')
|
.innerJoin('User as u', 'u.id', 'om.userId')
|
||||||
.where('om.organisationId', '=', organisationId)
|
.where('om.organisationId', '=', organisationId)
|
||||||
.select((eb) => [
|
.leftJoin('Envelope as e', (join) =>
|
||||||
'u.id',
|
join
|
||||||
'u.name',
|
.onRef('e.userId', '=', 'u.id')
|
||||||
'u.email',
|
.on('e.deletedAt', 'is', null)
|
||||||
'u.createdAt',
|
.on('e.type', '=', sql.lit(EnvelopeType.DOCUMENT)),
|
||||||
eb
|
)
|
||||||
.selectFrom('Envelope as e')
|
.leftJoin('Team as td', (join) =>
|
||||||
.innerJoin('Team as t', 't.id', 'e.teamId')
|
join.onRef('td.id', '=', 'e.teamId').on('td.organisationId', '=', organisationId),
|
||||||
.whereRef('e.userId', '=', 'u.id')
|
)
|
||||||
.where('t.organisationId', '=', organisationId)
|
.leftJoin('Recipient as r', (join) =>
|
||||||
.where('e.deletedAt', 'is', null)
|
join.onRef('r.email', '=', 'u.email').on('r.signedAt', 'is not', null),
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
)
|
||||||
.$if(!!createdAtFrom, (qb) => qb.where('e.createdAt', '>=', createdAtFrom!))
|
.leftJoin('Envelope as se', (join) =>
|
||||||
.select(sql<number>`count(e.id)`.as('count'))
|
join
|
||||||
.as('documentCount'),
|
.onRef('se.id', '=', 'r.envelopeId')
|
||||||
eb
|
.on('se.deletedAt', 'is', null)
|
||||||
.selectFrom('Recipient as r')
|
.on('se.type', '=', sql.lit(EnvelopeType.DOCUMENT)),
|
||||||
.innerJoin('Envelope as e', 'e.id', 'r.envelopeId')
|
)
|
||||||
.innerJoin('Team as t', 't.id', 'e.teamId')
|
.leftJoin('Team as ts', (join) =>
|
||||||
.whereRef('r.email', '=', 'u.email')
|
join.onRef('ts.id', '=', 'se.teamId').on('ts.organisationId', '=', organisationId),
|
||||||
.where('r.signedAt', 'is not', null)
|
);
|
||||||
.where('t.organisationId', '=', organisationId)
|
|
||||||
.where('e.deletedAt', 'is', null)
|
const usersQuery = usersBase
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
.select([
|
||||||
.$if(!!createdAtFrom, (qb) => qb.where('e.createdAt', '>=', createdAtFrom!))
|
'u.id as id',
|
||||||
.select(sql<number>`count(e.id)`.as('count'))
|
'u.name as name',
|
||||||
.as('signedDocumentCount'),
|
'u.email as email',
|
||||||
|
'u.createdAt as createdAt',
|
||||||
|
(createdAtFrom
|
||||||
|
? sql<number>`COUNT(DISTINCT CASE WHEN e.id IS NOT NULL AND td.id IS NOT NULL AND e."createdAt" >= ${createdAtFrom} THEN e.id END)`
|
||||||
|
: sql<number>`COUNT(DISTINCT CASE WHEN td.id IS NOT NULL THEN e.id END)`
|
||||||
|
).as('documentCount'),
|
||||||
|
(createdAtFrom
|
||||||
|
? sql<number>`COUNT(DISTINCT CASE WHEN e.id IS NOT NULL AND td.id IS NOT NULL AND e.status = 'COMPLETED' AND e."createdAt" >= ${createdAtFrom} THEN e.id END)`
|
||||||
|
: sql<number>`COUNT(DISTINCT CASE WHEN e.id IS NOT NULL AND td.id IS NOT NULL AND e.status = 'COMPLETED' THEN e.id END)`
|
||||||
|
).as('signedDocumentCount'),
|
||||||
])
|
])
|
||||||
|
.groupBy(['u.id', 'u.name', 'u.email', 'u.createdAt'])
|
||||||
.orderBy('u.createdAt', 'desc')
|
.orderBy('u.createdAt', 'desc')
|
||||||
.limit(perPage)
|
.limit(perPage)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
@ -282,51 +292,72 @@ async function getOrganisationSummary(
|
|||||||
organisationId: string,
|
organisationId: string,
|
||||||
createdAtFrom: Date | null,
|
createdAtFrom: Date | null,
|
||||||
): Promise<OrganisationSummary> {
|
): Promise<OrganisationSummary> {
|
||||||
const teamCountQuery = kyselyPrisma.$kysely
|
const summaryQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Team')
|
.selectFrom('Organisation as o')
|
||||||
.where('organisationId', '=', organisationId)
|
.where('o.id', '=', organisationId)
|
||||||
.select(sql<number>`count(id)`.as('count'))
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
const memberCountQuery = kyselyPrisma.$kysely
|
|
||||||
.selectFrom('OrganisationMember')
|
|
||||||
.where('organisationId', '=', organisationId)
|
|
||||||
.select(sql<number>`count(id)`.as('count'))
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
const envelopeStatsQuery = kyselyPrisma.$kysely
|
|
||||||
.selectFrom('Envelope as e')
|
|
||||||
.innerJoin('Team as t', 't.id', 'e.teamId')
|
|
||||||
.where('t.organisationId', '=', organisationId)
|
|
||||||
.where('e.deletedAt', 'is', null)
|
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
|
||||||
.select([
|
.select([
|
||||||
sql<number>`count(e.id)`.as('totalDocuments'),
|
sql<number>`(SELECT COUNT(DISTINCT t2.id) FROM "Team" AS t2 WHERE t2."organisationId" = o.id)`.as(
|
||||||
sql<number>`count(case when e.status in ('DRAFT', 'PENDING') then 1 end)`.as(
|
'totalTeams',
|
||||||
'activeDocuments',
|
|
||||||
),
|
),
|
||||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('completedDocuments'),
|
sql<number>`(SELECT COUNT(DISTINCT om2."userId") FROM "OrganisationMember" AS om2 WHERE om2."organisationId" = o.id)`.as(
|
||||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('volumeAllTime'),
|
'totalMembers',
|
||||||
|
),
|
||||||
|
sql<number>`(
|
||||||
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id AND e2."deletedAt" IS NULL AND e2.type = 'DOCUMENT'
|
||||||
|
)`.as('totalDocuments'),
|
||||||
|
sql<number>`(
|
||||||
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id AND e2."deletedAt" IS NULL AND e2.type = 'DOCUMENT' AND e2.status IN ('DRAFT', 'PENDING')
|
||||||
|
)`.as('activeDocuments'),
|
||||||
|
sql<number>`(
|
||||||
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id AND e2."deletedAt" IS NULL AND e2.type = 'DOCUMENT' AND e2.status = 'COMPLETED'
|
||||||
|
)`.as('completedDocuments'),
|
||||||
(createdAtFrom
|
(createdAtFrom
|
||||||
? sql<number>`count(case when e.status = 'COMPLETED' and e."createdAt" >= ${createdAtFrom} then 1 end)`
|
? sql<number>`(
|
||||||
: sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id
|
||||||
|
AND e2."deletedAt" IS NULL
|
||||||
|
AND e2.type = 'DOCUMENT'
|
||||||
|
AND e2.status = 'COMPLETED'
|
||||||
|
AND e2."createdAt" >= ${createdAtFrom}
|
||||||
|
)`
|
||||||
|
: sql<number>`(
|
||||||
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id
|
||||||
|
AND e2."deletedAt" IS NULL
|
||||||
|
AND e2.type = 'DOCUMENT'
|
||||||
|
AND e2.status = 'COMPLETED'
|
||||||
|
)`
|
||||||
).as('volumeThisPeriod'),
|
).as('volumeThisPeriod'),
|
||||||
])
|
sql<number>`(
|
||||||
.executeTakeFirst();
|
SELECT COUNT(DISTINCT e2.id)
|
||||||
|
FROM "Envelope" AS e2
|
||||||
|
INNER JOIN "Team" AS t2 ON t2.id = e2."teamId"
|
||||||
|
WHERE t2."organisationId" = o.id AND e2."deletedAt" IS NULL AND e2.type = 'DOCUMENT' AND e2.status = 'COMPLETED'
|
||||||
|
)`.as('volumeAllTime'),
|
||||||
|
]);
|
||||||
|
|
||||||
const [teamCount, memberCount, envelopeStats] = await Promise.all([
|
const result = await summaryQuery.executeTakeFirst();
|
||||||
teamCountQuery,
|
|
||||||
memberCountQuery,
|
|
||||||
envelopeStatsQuery,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalTeams: Number(teamCount?.count || 0),
|
totalTeams: Number(result?.totalTeams || 0),
|
||||||
totalMembers: Number(memberCount?.count || 0),
|
totalMembers: Number(result?.totalMembers || 0),
|
||||||
totalDocuments: Number(envelopeStats?.totalDocuments || 0),
|
totalDocuments: Number(result?.totalDocuments || 0),
|
||||||
activeDocuments: Number(envelopeStats?.activeDocuments || 0),
|
activeDocuments: Number(result?.activeDocuments || 0),
|
||||||
completedDocuments: Number(envelopeStats?.completedDocuments || 0),
|
completedDocuments: Number(result?.completedDocuments || 0),
|
||||||
volumeThisPeriod: Number(envelopeStats?.volumeThisPeriod || 0),
|
volumeThisPeriod: Number(result?.volumeThisPeriod || 0),
|
||||||
volumeAllTime: Number(envelopeStats?.volumeAllTime || 0),
|
volumeAllTime: Number(result?.volumeAllTime || 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,32 +33,25 @@ export async function getSigningVolume({
|
|||||||
|
|
||||||
let findQuery = kyselyPrisma.$kysely
|
let findQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Organisation as o')
|
.selectFrom('Organisation as o')
|
||||||
.where((eb) =>
|
.leftJoin('Team as t', 'o.id', 't.organisationId')
|
||||||
eb.or([
|
.leftJoin('Envelope as e', (join) =>
|
||||||
eb('o.name', 'ilike', `%${search}%`),
|
join
|
||||||
eb.exists(
|
.onRef('t.id', '=', 'e.teamId')
|
||||||
eb
|
.on('e.status', '=', sql.lit(DocumentStatus.COMPLETED))
|
||||||
.selectFrom('Team as t')
|
.on('e.deletedAt', 'is', null)
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
.on('e.type', '=', sql.lit(EnvelopeType.DOCUMENT)),
|
||||||
.where('t.name', 'ilike', `%${search}%`),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.select((eb) => [
|
.where((eb) =>
|
||||||
|
eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]),
|
||||||
|
)
|
||||||
|
.select([
|
||||||
'o.id as id',
|
'o.id as id',
|
||||||
'o.createdAt as createdAt',
|
'o.createdAt as createdAt',
|
||||||
'o.customerId as customerId',
|
'o.customerId as customerId',
|
||||||
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
||||||
eb
|
sql<number>`COUNT(DISTINCT e.id)`.as('signingVolume'),
|
||||||
.selectFrom('Envelope as e')
|
])
|
||||||
.innerJoin('Team as t', 't.id', 'e.teamId')
|
.groupBy(['o.id', 'o.name', 'o.customerId']);
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.where('e.status', '=', sql.lit(DocumentStatus.COMPLETED))
|
|
||||||
.where('e.deletedAt', 'is', null)
|
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
|
||||||
.select(sql<number>`count(e.id)`.as('count'))
|
|
||||||
.as('signingVolume'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case 'name':
|
case 'name':
|
||||||
@ -78,18 +71,11 @@ export async function getSigningVolume({
|
|||||||
|
|
||||||
const countQuery = kyselyPrisma.$kysely
|
const countQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Organisation as o')
|
.selectFrom('Organisation as o')
|
||||||
|
.leftJoin('Team as t', 'o.id', 't.organisationId')
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]),
|
||||||
eb('o.name', 'ilike', `%${search}%`),
|
|
||||||
eb.exists(
|
|
||||||
eb
|
|
||||||
.selectFrom('Team as t')
|
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.where('t.name', 'ilike', `%${search}%`),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.select(({ fn }) => [fn.countAll().as('count')]);
|
.select(() => [sql<number>`COUNT(DISTINCT o.id)`.as('count')]);
|
||||||
|
|
||||||
const [results, [{ count }]] = await Promise.all([findQuery.execute(), countQuery.execute()]);
|
const [results, [{ count }]] = await Promise.all([findQuery.execute(), countQuery.execute()]);
|
||||||
|
|
||||||
@ -118,77 +104,64 @@ export async function getOrganisationInsights({
|
|||||||
const offset = Math.max(page - 1, 0) * perPage;
|
const offset = Math.max(page - 1, 0) * perPage;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let dateCondition = sql<boolean>`1=1`;
|
let dateCondition = sql`1=1`;
|
||||||
|
|
||||||
if (startDate && endDate) {
|
if (startDate && endDate) {
|
||||||
dateCondition = sql<boolean>`e."createdAt" >= ${startDate} AND e."createdAt" <= ${endDate}`;
|
dateCondition = sql`e."createdAt" >= ${startDate} AND e."createdAt" <= ${endDate}`;
|
||||||
} else {
|
} else {
|
||||||
switch (dateRange) {
|
switch (dateRange) {
|
||||||
case 'last30days': {
|
case 'last30days': {
|
||||||
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||||
dateCondition = sql<boolean>`e."createdAt" >= ${thirtyDaysAgo}`;
|
dateCondition = sql`e."createdAt" >= ${thirtyDaysAgo}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'last90days': {
|
case 'last90days': {
|
||||||
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
||||||
dateCondition = sql<boolean>`e."createdAt" >= ${ninetyDaysAgo}`;
|
dateCondition = sql`e."createdAt" >= ${ninetyDaysAgo}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'lastYear': {
|
case 'lastYear': {
|
||||||
const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
||||||
dateCondition = sql<boolean>`e."createdAt" >= ${oneYearAgo}`;
|
dateCondition = sql`e."createdAt" >= ${oneYearAgo}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'allTime':
|
case 'allTime':
|
||||||
default:
|
default:
|
||||||
dateCondition = sql<boolean>`1=1`;
|
dateCondition = sql`1=1`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let findQuery = kyselyPrisma.$kysely
|
let findQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Organisation as o')
|
.selectFrom('Organisation as o')
|
||||||
|
.leftJoin('Team as t', 'o.id', 't.organisationId')
|
||||||
|
.leftJoin('Envelope as e', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('t.id', '=', 'e.teamId')
|
||||||
|
.on('e.status', '=', sql.lit(DocumentStatus.COMPLETED))
|
||||||
|
.on('e.deletedAt', 'is', null)
|
||||||
|
.on('e.type', '=', sql.lit(EnvelopeType.DOCUMENT)),
|
||||||
|
)
|
||||||
|
.leftJoin('OrganisationMember as om', 'o.id', 'om.organisationId')
|
||||||
.leftJoin('Subscription as s', 'o.id', 's.organisationId')
|
.leftJoin('Subscription as s', 'o.id', 's.organisationId')
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]),
|
||||||
eb('o.name', 'ilike', `%${search}%`),
|
|
||||||
eb.exists(
|
|
||||||
eb
|
|
||||||
.selectFrom('Team as t')
|
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.where('t.name', 'ilike', `%${search}%`),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.select((eb) => [
|
.select([
|
||||||
'o.id as id',
|
'o.id as id',
|
||||||
'o.createdAt as createdAt',
|
'o.createdAt as createdAt',
|
||||||
'o.customerId as customerId',
|
'o.customerId as customerId',
|
||||||
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
||||||
|
sql<number>`COUNT(DISTINCT CASE WHEN e.id IS NOT NULL AND ${dateCondition} THEN e.id END)`.as(
|
||||||
|
'signingVolume',
|
||||||
|
),
|
||||||
|
sql<number>`GREATEST(COUNT(DISTINCT t.id), 1)`.as('teamCount'),
|
||||||
|
sql<number>`COUNT(DISTINCT om."userId")`.as('memberCount'),
|
||||||
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as(
|
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as(
|
||||||
'subscriptionStatus',
|
'subscriptionStatus',
|
||||||
),
|
),
|
||||||
eb
|
])
|
||||||
.selectFrom('Team as t')
|
.groupBy(['o.id', 'o.name', 'o.customerId', 's.status']);
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.select(sql<number>`count(t.id)`.as('count'))
|
|
||||||
.as('teamCount'),
|
|
||||||
eb
|
|
||||||
.selectFrom('OrganisationMember as om')
|
|
||||||
.whereRef('om.organisationId', '=', 'o.id')
|
|
||||||
.select(sql<number>`count(om.id)`.as('count'))
|
|
||||||
.as('memberCount'),
|
|
||||||
eb
|
|
||||||
.selectFrom('Envelope as e')
|
|
||||||
.innerJoin('Team as t', 't.id', 'e.teamId')
|
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.where('e.status', '=', sql.lit(DocumentStatus.COMPLETED))
|
|
||||||
.where('e.deletedAt', 'is', null)
|
|
||||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
|
||||||
.where(dateCondition)
|
|
||||||
.select(sql<number>`count(e.id)`.as('count'))
|
|
||||||
.as('signingVolume'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case 'name':
|
case 'name':
|
||||||
@ -208,18 +181,11 @@ export async function getOrganisationInsights({
|
|||||||
|
|
||||||
const countQuery = kyselyPrisma.$kysely
|
const countQuery = kyselyPrisma.$kysely
|
||||||
.selectFrom('Organisation as o')
|
.selectFrom('Organisation as o')
|
||||||
|
.leftJoin('Team as t', 'o.id', 't.organisationId')
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]),
|
||||||
eb('o.name', 'ilike', `%${search}%`),
|
|
||||||
eb.exists(
|
|
||||||
eb
|
|
||||||
.selectFrom('Team as t')
|
|
||||||
.whereRef('t.organisationId', '=', 'o.id')
|
|
||||||
.where('t.name', 'ilike', `%${search}%`),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.select(({ fn }) => [fn.countAll().as('count')]);
|
.select(() => [sql<number>`COUNT(DISTINCT o.id)`.as('count')]);
|
||||||
|
|
||||||
const [results, [{ count }]] = await Promise.all([findQuery.execute(), countQuery.execute()]);
|
const [results, [{ count }]] = await Promise.all([findQuery.execute(), countQuery.execute()]);
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Envelope_teamId_deletedAt_type_status_idx" ON "Envelope"("teamId", "deletedAt", "type", "status");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Envelope_teamId_deletedAt_type_createdAt_idx" ON "Envelope"("teamId", "deletedAt", "type", "createdAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Envelope_userId_deletedAt_type_idx" ON "Envelope"("userId", "deletedAt", "type");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Envelope_status_deletedAt_type_idx" ON "Envelope"("status", "deletedAt", "type");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Organisation_name_idx" ON "Organisation"("name");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "OrganisationMember_organisationId_idx" ON "OrganisationMember"("organisationId");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Recipient_email_idx" ON "Recipient"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Recipient_signedAt_idx" ON "Recipient"("signedAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Recipient_envelopeId_signedAt_idx" ON "Recipient"("envelopeId", "signedAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Team_organisationId_name_idx" ON "Team"("organisationId", "name");
|
|
||||||
@ -433,10 +433,6 @@ model Envelope {
|
|||||||
@@index([folderId])
|
@@index([folderId])
|
||||||
@@index([teamId])
|
@@index([teamId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([teamId, deletedAt, type, status])
|
|
||||||
@@index([teamId, deletedAt, type, createdAt])
|
|
||||||
@@index([userId, deletedAt, type])
|
|
||||||
@@index([status, deletedAt, type])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model EnvelopeItem {
|
model EnvelopeItem {
|
||||||
@ -589,9 +585,6 @@ model Recipient {
|
|||||||
|
|
||||||
@@index([envelopeId])
|
@@index([envelopeId])
|
||||||
@@index([token])
|
@@index([token])
|
||||||
@@index([email])
|
|
||||||
@@index([signedAt])
|
|
||||||
@@index([envelopeId, signedAt])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FieldType {
|
enum FieldType {
|
||||||
@ -701,8 +694,6 @@ model Organisation {
|
|||||||
|
|
||||||
organisationAuthenticationPortalId String @unique
|
organisationAuthenticationPortalId String @unique
|
||||||
organisationAuthenticationPortal OrganisationAuthenticationPortal @relation(fields: [organisationAuthenticationPortalId], references: [id])
|
organisationAuthenticationPortal OrganisationAuthenticationPortal @relation(fields: [organisationAuthenticationPortalId], references: [id])
|
||||||
|
|
||||||
@@index([name])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model OrganisationMember {
|
model OrganisationMember {
|
||||||
@ -719,7 +710,6 @@ model OrganisationMember {
|
|||||||
organisationGroupMembers OrganisationGroupMember[]
|
organisationGroupMembers OrganisationGroupMember[]
|
||||||
|
|
||||||
@@unique([userId, organisationId])
|
@@unique([userId, organisationId])
|
||||||
@@index([organisationId])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model OrganisationMemberInvite {
|
model OrganisationMemberInvite {
|
||||||
@ -894,7 +884,6 @@ model Team {
|
|||||||
teamGlobalSettings TeamGlobalSettings @relation(fields: [teamGlobalSettingsId], references: [id], onDelete: Cascade)
|
teamGlobalSettings TeamGlobalSettings @relation(fields: [teamGlobalSettingsId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([organisationId])
|
@@index([organisationId])
|
||||||
@@index([organisationId, name])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model TeamEmail {
|
model TeamEmail {
|
||||||
|
|||||||
@ -127,11 +127,11 @@ export const DocumentShareButton = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!token || !documentId}
|
disabled={!token || !documentId}
|
||||||
className={cn('flex-1 text-[11px]', className)}
|
className={cn('w-full max-w-lg flex-1 text-[11px]', className)}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
{!isLoading && <Sparkles className="mr-2 h-5 w-5" />}
|
{!isLoading && <Sparkles className="mr-2 h-5 w-5" />}
|
||||||
<Trans>Share Signature Card</Trans>
|
<Trans>Share</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
Reference in New Issue
Block a user