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 { 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 { trpc } from '@documenso/trpc/react'; import { type TFindDocumentsInternalResponse, 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'; 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 { useOptionalCurrentTeam } from '~/providers/team'; import { appMetaTags } from '~/utils/meta'; export function meta() { return appMetaTags('Documents'); } const ZSearchParamsSchema = ZFindDocumentsInternalRequestSchema.pick({ status: true, period: true, page: true, perPage: true, query: true, }).extend({ senderIds: z.string().transform(parseToIntegerArray).optional().catch([]), }); export default function DocumentsPage() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [isMovingDocument, setIsMovingDocument] = useState(false); const [documentToMove, setDocumentToMove] = useState(null); const [isMovingFolder, setIsMovingFolder] = useState(false); const [folderToMove, setFolderToMove] = useState(null); const [isDeletingFolder, setIsDeletingFolder] = useState(false); const [folderToDelete, setFolderToDelete] = useState(null); const [isSettingsFolderOpen, setIsSettingsFolderOpen] = useState(false); const [folderToSettings, setFolderToSettings] = useState(null); const { folderId } = useParams(); const team = useOptionalCurrentTeam(); 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], ); const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocumentsInternal.useQuery( { ...findDocumentSearchParams, folderId, }, ); const { data: foldersData, isLoading: isFoldersLoading, refetch: refetchFolders, } = trpc.folder.getFolders.useQuery({ parentId: folderId, }); useEffect(() => { void refetch(); 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); if (folderId) { void navigate(`${documentsPath}/f/${folderId}`); } else { void navigate(documentsPath); } }; return (
{foldersData?.breadcrumbs.map((folder) => (
/
))}
{isFoldersLoading ? (
) : ( <> {foldersData?.folders && foldersData.folders.some((folder) => folder.pinned) && (
{foldersData.folders .filter((folder) => folder.pinned) .map((folder) => ( { setFolderToMove(folder); setIsMovingFolder(true); }} onPin={(folderId) => void pinFolder({ folderId })} onUnpin={(folderId) => void unpinFolder({ folderId })} onSettings={(folder) => { setFolderToSettings(folder); setIsSettingsFolderOpen(true); }} onDelete={(folder) => { setFolderToDelete(folder); setIsDeletingFolder(true); }} /> ))}
)}
{foldersData?.folders .filter((folder) => !folder.pinned) .map((folder) => ( { setFolderToMove(folder); setIsMovingFolder(true); }} onPin={(folderId) => void pinFolder({ folderId })} onUnpin={(folderId) => void unpinFolder({ folderId })} onSettings={(folder) => { setFolderToSettings(folder); setIsSettingsFolderOpen(true); }} onDelete={(folder) => { setFolderToDelete(folder); setIsDeletingFolder(true); }} /> ))}
)}
{team && ( {team.avatarImageId && } {team.name.slice(0, 1)} )}

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); }} /> )}
{documentToMove && ( { setIsMovingDocument(open); if (!open) { setDocumentToMove(null); } }} currentFolderId={folderId} /> )} { setIsMovingFolder(open); if (!open) { setFolderToMove(null); } }} /> { setIsSettingsFolderOpen(open); if (!open) { setFolderToSettings(null); } }} /> { setIsDeletingFolder(open); if (!open) { setFolderToDelete(null); } }} />
); }