refactor(inbox): move partially approved into extended document status

This commit is contained in:
ephraimduncan
2026-09-24 12:11:01 +00:00
parent f2f7ba2878
commit 0823c3328f
10 changed files with 34 additions and 30 deletions
@@ -1,5 +1,4 @@
import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { TInboxStatus } from '@documenso/trpc/server/document-router/find-inbox.types';
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { cn } from '@documenso/ui/lib/utils';
import type { MessageDescriptor } from '@lingui/core';
@@ -16,7 +15,7 @@ type FriendlyStatus = {
color: string;
};
export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus | TInboxStatus, FriendlyStatus> = {
export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus> = {
PENDING: {
label: msg`Pending`,
labelExtended: msg`Document pending`,
@@ -73,7 +72,7 @@ export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus | TInboxStatus,
};
export type DocumentStatusProps = HTMLAttributes<HTMLSpanElement> & {
status: ExtendedDocumentStatus | TInboxStatus;
status: ExtendedDocumentStatus;
inheritColor?: boolean;
};
@@ -1,32 +1,29 @@
import { useOptionalCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { STATS_COUNT_CAP } from '@documenso/lib/constants/document';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { TFindDocumentsInternalResponse } from '@documenso/trpc/server/document-router/find-documents-internal.types';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationType } from '@prisma/client';
import { ListFilterIcon } from 'lucide-react';
import { parseAsString, useQueryStates } from 'nuqs';
import { useQueryStates } from 'nuqs';
import { useMemo } from 'react';
import type { DocumentStatusProps } from '~/components/general/document/document-status';
import { DocumentStatus, FRIENDLY_STATUS_MAP } from '~/components/general/document/document-status';
import { FilterPill } from '~/components/general/filter-pill';
import { documentsSearchParams } from '~/utils/documents-search-params';
type DocumentsTableStatusFilterProps = {
/**
* Per-status document counts, shown next to each option. When omitted no
* counts are rendered.
* Per-status document counts, shown next to each option. When omitted, or
* when a status has no count, no count is rendered.
*/
stats?: TFindDocumentsInternalResponse['stats'];
stats?: Partial<Record<ExtendedDocumentStatus, number>>;
/**
* The statuses available for selection. Defaults to every status that
* makes sense for the documents page.
*/
statuses?: readonly DocumentStatusProps['status'][];
statuses?: readonly ExtendedDocumentStatus[];
};
export const DocumentsTableStatusFilter = ({
@@ -37,11 +34,9 @@ export const DocumentsTableStatusFilter = ({
const organisation = useOptionalCurrentOrganisation();
// Any string is read here since each page offers its own statuses, which
// are matched against the selectable statuses below.
const [{ status }, setSearchParams] = useQueryStates(
{
status: parseAsString,
status: documentsSearchParams.status,
page: documentsSearchParams.page,
},
{ history: 'push' },
@@ -82,7 +77,7 @@ export const DocumentsTableStatusFilter = ({
options={selectableStatuses.map((value) => ({
value,
label: <DocumentStatus status={value} />,
trailing: stats && isExtendedDocumentStatus(value) ? formatStatsCount(stats[value]) : undefined,
trailing: formatStatsCount(stats?.[value]),
}))}
testId="documents-table-status-filter"
/>
@@ -90,7 +85,7 @@ export const DocumentsTableStatusFilter = ({
{/* Visually hidden document counts, for screen readers and tests. */}
{stats && (
<span className="sr-only" data-testid="documents-status-counts">
{[...selectableStatuses, ExtendedDocumentStatus.ALL].filter(isExtendedDocumentStatus).map((value) => (
{[...selectableStatuses, ExtendedDocumentStatus.ALL].map((value) => (
<span key={value}>
{_(FRIENDLY_STATUS_MAP[value].label)}:{' '}
<span data-testid={`documents-status-count-${value}`}>{stats[value]}</span>
@@ -112,6 +107,10 @@ const SELECTABLE_STATUSES: ExtendedDocumentStatus[] = [
ExtendedDocumentStatus.EXPIRED,
];
const formatStatsCount = (count: number) => {
const formatStatsCount = (count: number | undefined) => {
if (count === undefined) {
return undefined;
}
return count >= STATS_COUNT_CAP ? `${STATS_COUNT_CAP.toLocaleString()}+` : count.toString();
};
@@ -1,6 +1,7 @@
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { trpc } from '@documenso/trpc/react';
import type { TFindInboxResponse } from '@documenso/trpc/server/document-router/find-inbox.types';
import { Button } from '@documenso/ui/primitives/button';
@@ -131,7 +132,7 @@ export const InboxTable = () => {
.with({ status: DocumentStatusEnum.COMPLETED }, () => (
<Trans>Documents that you have completed will appear here</Trans>
))
.with({ status: 'PARTIALLY_APPROVED' }, () => (
.with({ status: ExtendedDocumentStatus.PARTIALLY_APPROVED }, () => (
<Trans>Documents that are waiting on other recipients will appear here</Trans>
))
.with({ status: DocumentStatusEnum.REJECTED }, () => (
@@ -278,5 +279,5 @@ const getInboxStatus = (row: DocumentsTableRow, email: string) => {
recipient.signingStatus !== SigningStatus.NOT_SIGNED,
);
return isWaitingOnOthers ? 'PARTIALLY_APPROVED' : row.status;
return isWaitingOnOthers ? ExtendedDocumentStatus.PARTIALLY_APPROVED : row.status;
};
@@ -8,6 +8,7 @@ import {
} from '@documenso/prisma/seed/documents';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { DocumentStatus, RecipientRole, SigningStatus, TeamMemberRole } from '@prisma/client';
@@ -37,7 +38,7 @@ const searchInbox = async (page: Page, query: string) => {
// spelling "Canceled" for the `Cancelled` source string.
const INBOX_STATUS_LABELS = {
[DocumentStatus.PENDING]: 'Pending',
PARTIALLY_APPROVED: 'Partially Approved',
[ExtendedDocumentStatus.PARTIALLY_APPROVED]: 'Partially Approved',
[DocumentStatus.COMPLETED]: 'Completed',
[DocumentStatus.REJECTED]: 'Rejected',
[DocumentStatus.CANCELLED]: 'Canceled',
@@ -154,7 +155,7 @@ test.describe('Inbox - Search & Status Filter', () => {
await expect(inboxRow(page, 'Inbox Unsigned Document')).toBeVisible();
await expect(inboxRow(page, 'Inbox Signed Document')).not.toBeVisible();
await selectInboxStatus(page, 'PARTIALLY_APPROVED');
await selectInboxStatus(page, ExtendedDocumentStatus.PARTIALLY_APPROVED);
await expect(page.getByTestId('documents-table-status-filter')).toContainText('Partially Approved');
await expect(inboxRow(page, 'Inbox Signed Document')).toBeVisible();
@@ -13,7 +13,7 @@ export const getDocumentStats = async () => {
},
});
const stats: Record<Exclude<ExtendedDocumentStatus, 'INBOX' | 'EXPIRED'>, number> = {
const stats: Record<Exclude<ExtendedDocumentStatus, 'INBOX' | 'EXPIRED' | 'PARTIALLY_APPROVED'>, number> = {
[ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0,
@@ -326,6 +326,7 @@ export const findDocuments = async ({
]),
),
)
.with(ExtendedDocumentStatus.PARTIALLY_APPROVED, () => null) // Only the inbox offers this status.
.exhaustive();
};
@@ -488,6 +489,7 @@ export const findDocuments = async ({
return eb.and([teamDeletedFilter(eb), visibilityFilter(eb), hasExpiredRecipient(eb), eb.or(accessBranches)]);
}),
)
.with(ExtendedDocumentStatus.PARTIALLY_APPROVED, () => null) // Only the inbox offers this status.
.exhaustive();
};
@@ -301,7 +301,7 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
// `expired` is intentionally excluded from `all` — it overlaps PENDING.
const all = Math.min(draft + pending + completed + rejected + cancelled + inbox, STATS_COUNT_CAP);
const stats: Record<ExtendedDocumentStatus, number> = {
const stats: Record<Exclude<ExtendedDocumentStatus, 'PARTIALLY_APPROVED'>, number> = {
[ExtendedDocumentStatus.DRAFT]: draft,
[ExtendedDocumentStatus.PENDING]: pending,
[ExtendedDocumentStatus.COMPLETED]: completed,
@@ -5,6 +5,7 @@ export const ExtendedDocumentStatus = {
INBOX: 'INBOX',
ALL: 'ALL',
EXPIRED: 'EXPIRED',
PARTIALLY_APPROVED: 'PARTIALLY_APPROVED',
} as const;
export type ExtendedDocumentStatus = (typeof ExtendedDocumentStatus)[keyof typeof ExtendedDocumentStatus];
@@ -1,6 +1,7 @@
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
import { prisma } from '@documenso/prisma';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { Envelope, Prisma } from '@prisma/client';
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
import { match, P } from 'ts-pattern';
@@ -85,7 +86,7 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, query = '', st
status: DocumentStatus.PENDING,
recipients: { some: unsignedUserRecipient },
}))
.with('PARTIALLY_APPROVED', () => ({
.with(ExtendedDocumentStatus.PARTIALLY_APPROVED, () => ({
status: DocumentStatus.PENDING,
recipients: { some: userRecipient, none: unsignedUserRecipient },
}))
@@ -2,7 +2,7 @@
import { ZDocumentManySchema } from '@documenso/lib/types/document';
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { DocumentStatus } from '@prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { z } from 'zod';
/**
@@ -16,11 +16,11 @@ import { z } from 'zod';
* that wait on other recipients.
*/
export const INBOX_STATUSES = [
DocumentStatus.PENDING,
'PARTIALLY_APPROVED',
DocumentStatus.COMPLETED,
DocumentStatus.REJECTED,
DocumentStatus.CANCELLED,
ExtendedDocumentStatus.PENDING,
ExtendedDocumentStatus.PARTIALLY_APPROVED,
ExtendedDocumentStatus.COMPLETED,
ExtendedDocumentStatus.REJECTED,
ExtendedDocumentStatus.CANCELLED,
] as const;
export const ZInboxStatusSchema = z.enum(INBOX_STATUSES);