diff --git a/apps/remix/app/components/tables/documents-table-empty-state.tsx b/apps/remix/app/components/tables/documents-table-empty-state.tsx index e02a1c2bd..e81e399a7 100644 --- a/apps/remix/app/components/tables/documents-table-empty-state.tsx +++ b/apps/remix/app/components/tables/documents-table-empty-state.tsx @@ -25,6 +25,21 @@ export const DocumentsTableEmptyState = ({ status }: DocumentsTableEmptyStatePro message: msg`There are no active drafts at the current moment. You can upload a document to start drafting.`, icon: CheckCircle2, })) + .with(ExtendedDocumentStatus.PENDING, () => ({ + title: msg`No pending documents`, + message: msg`There are no pending documents at the moment. Documents awaiting signatures will appear here.`, + icon: CheckCircle2, + })) + .with(ExtendedDocumentStatus.REJECTED, () => ({ + title: msg`No rejected documents`, + message: msg`There are no rejected documents. Documents that have been declined will appear here.`, + icon: CheckCircle2, + })) + .with(ExtendedDocumentStatus.INBOX, () => ({ + title: msg`Your inbox is empty`, + message: msg`There are no documents waiting for your action. Documents requiring your signature will appear here.`, + icon: CheckCircle2, + })) .with(ExtendedDocumentStatus.ALL, () => ({ title: msg`We're all empty`, message: msg`You have not yet created or received any documents. To create a document please upload one.`, @@ -38,7 +53,7 @@ export const DocumentsTableEmptyState = ({ status }: DocumentsTableEmptyStatePro return (
diff --git a/apps/remix/app/components/tables/documents-table/data-table.tsx b/apps/remix/app/components/tables/documents-table/data-table.tsx index ccd1df0f6..435246c62 100644 --- a/apps/remix/app/components/tables/documents-table/data-table.tsx +++ b/apps/remix/app/components/tables/documents-table/data-table.tsx @@ -12,6 +12,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda import { useSession } from '@documenso/lib/client-only/providers/session'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; +import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import type { TFindDocumentsInternalResponse } from '@documenso/trpc/server/document-router/schema'; import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; @@ -29,6 +30,7 @@ import { useOptionalCurrentTeam } from '~/providers/team'; import { DocumentsTableActionButton } from '../documents-table-action-button'; import { DocumentsTableActionDropdown } from '../documents-table-action-dropdown'; +import { DocumentsTableEmptyState } from '../documents-table-empty-state'; export type DataTableProps = { data?: TFindDocumentsInternalResponse; @@ -164,6 +166,13 @@ export function DocumentsDataTable({ totalPages: 1, }; + const getEmptyStateStatus = (): ExtendedDocumentStatus => { + if (selectedStatusValues.length > 0) { + return selectedStatusValues[0] as ExtendedDocumentStatus; + } + return ExtendedDocumentStatus.ALL; + }; + return (
), }} + emptyState={{ + enable: !isLoading && !isLoadingError, + component: , + }} > {(table) => } diff --git a/apps/remix/app/routes/_authenticated+/documents._index.tsx b/apps/remix/app/routes/_authenticated+/documents._index.tsx index fa88a7baf..701cdb35a 100644 --- a/apps/remix/app/routes/_authenticated+/documents._index.tsx +++ b/apps/remix/app/routes/_authenticated+/documents._index.tsx @@ -10,7 +10,7 @@ import { FolderType } from '@documenso/lib/types/folder-type'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; import { parseToIntegerArray } from '@documenso/lib/utils/params'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; -import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; +import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { trpc } from '@documenso/trpc/react'; import { ZFindDocumentsInternalRequestSchema } from '@documenso/trpc/server/document-router/schema'; import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema'; @@ -26,7 +26,6 @@ import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialo import { DocumentDropZoneWrapper } from '~/components/general/document/document-drop-zone-wrapper'; import { DocumentUploadDropzone } from '~/components/general/document/document-upload'; import { FolderCard } from '~/components/general/folder/folder-card'; -import { DocumentsTableEmptyState } from '~/components/tables/documents-table-empty-state'; import { DocumentsDataTable } from '~/components/tables/documents-table/data-table'; import { useOptionalCurrentTeam } from '~/providers/team'; import { appMetaTags } from '~/utils/meta'; @@ -248,27 +247,15 @@ export default function DocumentsPage() {
- {data && - data.count === 0 && - (!foldersData?.folders.length || foldersData.folders.length === 0) ? ( - - ) : ( - { - setDocumentToMove(documentId); - setIsMovingDocument(true); - }} - /> - )} + { + setDocumentToMove(documentId); + setIsMovingDocument(true); + }} + />
diff --git a/apps/remix/app/routes/_authenticated+/documents.f.$folderId._index.tsx b/apps/remix/app/routes/_authenticated+/documents.f.$folderId._index.tsx index ad5f40700..239ae3cc2 100644 --- a/apps/remix/app/routes/_authenticated+/documents.f.$folderId._index.tsx +++ b/apps/remix/app/routes/_authenticated+/documents.f.$folderId._index.tsx @@ -3,22 +3,18 @@ import { useEffect, useMemo, useState } from 'react'; import { Trans } from '@lingui/react/macro'; import { FolderIcon, HomeIcon, Loader2 } from 'lucide-react'; import { useNavigate, useParams, useSearchParams } from 'react-router'; -import { Link } from 'react-router'; import { z } from 'zod'; +import { FolderType } from '@documenso/lib/types/folder-type'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; import { parseToIntegerArray } from '@documenso/lib/utils/params'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; -import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; +import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { trpc } from '@documenso/trpc/react'; -import { - type TFindDocumentsInternalResponse, - ZFindDocumentsInternalRequestSchema, -} from '@documenso/trpc/server/document-router/schema'; +import { ZFindDocumentsInternalRequestSchema } from '@documenso/trpc/server/document-router/schema'; import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema'; import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; -import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { DocumentMoveToFolderDialog } from '~/components/dialogs/document-move-to-folder-dialog'; import { CreateFolderDialog } from '~/components/dialogs/folder-create-dialog'; @@ -26,14 +22,9 @@ import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog'; import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog'; import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog'; import { DocumentDropZoneWrapper } from '~/components/general/document/document-drop-zone-wrapper'; -import { DocumentSearch } from '~/components/general/document/document-search'; -import { DocumentStatus } from '~/components/general/document/document-status'; import { DocumentUploadDropzone } from '~/components/general/document/document-upload'; import { FolderCard } from '~/components/general/folder/folder-card'; -import { PeriodSelector } from '~/components/general/period-selector'; -import { DocumentsTable } from '~/components/tables/documents-table'; -import { DocumentsTableEmptyState } from '~/components/tables/documents-table-empty-state'; -import { DocumentsTableSenderFilter } from '~/components/tables/documents-table-sender-filter'; +import { DocumentsDataTable } from '~/components/tables/documents-table/data-table'; import { useOptionalCurrentTeam } from '~/providers/team'; import { appMetaTags } from '~/utils/meta'; @@ -42,13 +33,23 @@ export function meta() { } const ZSearchParamsSchema = ZFindDocumentsInternalRequestSchema.pick({ - status: true, period: true, page: true, perPage: true, query: true, }).extend({ senderIds: z.string().transform(parseToIntegerArray).optional().catch([]), + status: z + .string() + .transform( + (val) => + val + .split(',') + .map((s) => s.trim()) + .filter(Boolean) as ExtendedDocumentStatus[], + ) + .optional() + .catch(undefined), }); export default function DocumentsPage() { @@ -71,15 +72,6 @@ export default function DocumentsPage() { const { mutateAsync: pinFolder } = trpc.folder.pinFolder.useMutation(); const { mutateAsync: unpinFolder } = trpc.folder.unpinFolder.useMutation(); - const [stats, setStats] = useState({ - [ExtendedDocumentStatus.DRAFT]: 0, - [ExtendedDocumentStatus.PENDING]: 0, - [ExtendedDocumentStatus.COMPLETED]: 0, - [ExtendedDocumentStatus.REJECTED]: 0, - [ExtendedDocumentStatus.INBOX]: 0, - [ExtendedDocumentStatus.ALL]: 0, - }); - const findDocumentSearchParams = useMemo( () => ZSearchParamsSchema.safeParse(Object.fromEntries(searchParams.entries())).data || {}, [searchParams], @@ -97,6 +89,7 @@ export default function DocumentsPage() { isLoading: isFoldersLoading, refetch: refetchFolders, } = trpc.folder.getFolders.useQuery({ + type: FolderType.DOCUMENT, parentId: folderId, }); @@ -105,28 +98,6 @@ export default function DocumentsPage() { void refetchFolders(); }, [team?.url]); - const getTabHref = (value: keyof typeof ExtendedDocumentStatus) => { - const params = new URLSearchParams(searchParams); - - params.set('status', value); - - if (value === ExtendedDocumentStatus.ALL) { - params.delete('status'); - } - - if (params.has('page')) { - params.delete('page'); - } - - return `${formatDocumentsPath(team?.url)}/f/${folderId}?${params.toString()}`; - }; - - useEffect(() => { - if (data?.stats) { - setStats(data.stats); - } - }, [data?.stats]); - const navigateToFolder = (folderId?: string | null) => { const documentsPath = formatDocumentsPath(team?.url); @@ -255,65 +226,19 @@ export default function DocumentsPage() { Documents
- -
- - - {[ - ExtendedDocumentStatus.INBOX, - ExtendedDocumentStatus.PENDING, - ExtendedDocumentStatus.COMPLETED, - ExtendedDocumentStatus.DRAFT, - ExtendedDocumentStatus.ALL, - ].map((value) => ( - - - - - {value !== ExtendedDocumentStatus.ALL && ( - {stats[value]} - )} - - - ))} - - - - {team && } - -
- -
-
- -
-
- {data && - data.count === 0 && - (!foldersData?.folders.length || foldersData.folders.length === 0) ? ( - - ) : ( - { - setDocumentToMove(documentId); - setIsMovingDocument(true); - }} - /> - )} + { + setDocumentToMove(documentId); + setIsMovingDocument(true); + }} + />
diff --git a/packages/ui/primitives/data-table/data-table.tsx b/packages/ui/primitives/data-table/data-table.tsx index 531514921..0eb01579e 100644 --- a/packages/ui/primitives/data-table/data-table.tsx +++ b/packages/ui/primitives/data-table/data-table.tsx @@ -52,6 +52,10 @@ interface DataTableProps { enable: boolean; component?: React.ReactNode; }; + emptyState?: { + enable: boolean; + component?: React.ReactNode; + }; } export function DataTable({ @@ -72,6 +76,7 @@ export function DataTable({ onResetFilters, isStatusFiltered, isTimePeriodFiltered, + emptyState, }: DataTableProps) { const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); @@ -149,61 +154,65 @@ export function DataTable({ isStatusFiltered={isStatusFiltered} isTimePeriodFiltered={isTimePeriodFiltered} /> -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {table.getRowModel().rows?.length || error?.enable || skeleton?.enable ? ( +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : error?.enable ? ( + + {error.component ?? ( + + Something went wrong. - ))} + )} - )) - ) : error?.enable ? ( - - {error.component ?? ( - - Something went wrong. - - )} - - ) : skeleton?.enable ? ( - Array.from({ length: skeleton.rows }).map((_, i) => ( - - {skeleton.component ?? } - - )) - ) : ( - - - No results. - - - )} - -
-
+ ) : skeleton?.enable ? ( + Array.from({ length: skeleton.rows }).map((_, i) => ( + + {skeleton.component ?? } + + )) + ) : null} + + + + ) : emptyState?.enable ? ( + (emptyState.component ?? ( +
No results.
+ )) + ) : ( +
No results.
+ )} - {children &&
{children(table)}
} + {children && (table.getRowModel().rows?.length || error?.enable || skeleton?.enable) && ( +
{children(table)}
+ )} ); } diff --git a/packages/ui/primitives/data-table/data/data.tsx b/packages/ui/primitives/data-table/data/data.tsx index 71b56c7c6..491435ec2 100644 --- a/packages/ui/primitives/data-table/data/data.tsx +++ b/packages/ui/primitives/data-table/data/data.tsx @@ -1,6 +1,13 @@ import { CheckCircle2, Clock, File, Inbox, XCircle } from 'lucide-react'; export const statuses = [ + { + value: 'INBOX', + label: 'Inbox', + icon: Inbox, + color: 'text-blue-700 dark:text-blue-300', + bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700', + }, { value: 'DRAFT', label: 'Draft', @@ -29,13 +36,6 @@ export const statuses = [ color: 'text-red-700 dark:text-red-300', bgColor: 'bg-red-100 dark:bg-red-100 text-red-500 dark:text-red-700', }, - { - value: 'INBOX', - label: 'Inbox', - icon: Inbox, - color: 'text-blue-700 dark:text-blue-300', - bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700', - }, ]; export const timePeriods = [ diff --git a/packages/ui/primitives/data-table/user-nav.tsx b/packages/ui/primitives/data-table/user-nav.tsx deleted file mode 100644 index f45fab014..000000000 --- a/packages/ui/primitives/data-table/user-nav.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage } from '../avatar'; -import { Button } from '../button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuTrigger, -} from '../dropdown-menu'; - -export function UserNav() { - return ( - - - - - - -
-

shadcn

-

m@example.com

-
-
- - - - Profile - ⇧⌘P - - - Billing - ⌘B - - - Settings - ⌘S - - New Team - - - - Log out - ⇧⌘Q - -
-
- ); -}