import { useMemo, useTransition } from 'react'; import { i18n } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Loader } from 'lucide-react'; import { DateTime } from 'luxon'; import { Link, useSearchParams } from 'react-router'; import { match } from 'ts-pattern'; 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 { formatDocumentsPath } from '@documenso/lib/utils/teams'; 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'; import { DataTable } from '@documenso/ui/primitives/data-table/data-table'; import { type TimePeriod, isDateInPeriod, } from '@documenso/ui/primitives/data-table/utils/time-filters'; import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; import { DocumentStatus } from '~/components/general/document/document-status'; import { StackAvatarsWithTooltip } from '~/components/general/stack-avatars-with-tooltip'; import { useOptionalCurrentTeam } from '~/providers/team'; import { DocumentsTableActionButton } from '../documents-table-action-button'; import { DocumentsTableActionDropdown } from '../documents-table-action-dropdown'; export type DataTableProps = { data?: TFindDocumentsInternalResponse; isLoading?: boolean; isLoadingError?: boolean; onMoveDocument?: (documentId: number) => void; }; type DocumentsTableRow = TFindDocumentsInternalResponse['data'][number]; export function DocumentsDataTable({ data, isLoading, isLoadingError, onMoveDocument, }: DataTableProps) { const { _ } = useLingui(); const team = useOptionalCurrentTeam(); const [isPending, startTransition] = useTransition(); const updateSearchParams = useUpdateSearchParams(); const [searchParams] = useSearchParams(); const handleStatusFilterChange = (values: string[]) => { startTransition(() => { if (values.length === 0) { updateSearchParams({ status: undefined, page: undefined }); } else { updateSearchParams({ status: values.join(','), page: undefined }); } }); }; const currentStatus = searchParams.get('status'); const selectedStatusValues = currentStatus ? currentStatus.split(',').filter(Boolean) : []; const handleResetFilters = () => { startTransition(() => { updateSearchParams({ status: undefined, period: undefined, page: undefined }); }); }; const isStatusFiltered = selectedStatusValues.length > 0; const handleTimePeriodFilterChange = (values: string[]) => { startTransition(() => { if (values.length === 0) { updateSearchParams({ period: undefined, page: undefined }); } else { updateSearchParams({ period: values[0], page: undefined }); } }); }; const currentPeriod = searchParams.get('period'); const selectedTimePeriodValues = currentPeriod ? [currentPeriod] : []; const isTimePeriodFiltered = selectedTimePeriodValues.length > 0; const columns = useMemo(() => { return [ { header: 'Created', accessorKey: 'createdAt', cell: ({ row }) => i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }), filterFn: (row, id, value) => { const createdAt = row.getValue(id) as Date; if (!value || !Array.isArray(value) || value.length === 0) { return true; } const period = value[0] as TimePeriod; return isDateInPeriod(createdAt, period); }, }, { header: _(msg`Title`), accessorKey: 'title', cell: ({ row }) => , }, { id: 'sender', header: _(msg`Sender`), cell: ({ row }) => row.original.user.name ?? row.original.user.email, }, { header: _(msg`Recipient`), accessorKey: 'recipient', cell: ({ row }) => ( ), }, { header: _(msg`Status`), accessorKey: 'status', cell: ({ row }) => , size: 140, filterFn: (row, id, value) => { return value.includes(row.getValue(id)); }, }, { header: _(msg`Actions`), cell: ({ row }) => (!row.original.deletedAt || isDocumentCompleted(row.original.status)) && (
onMoveDocument(row.original.id) : undefined} />
), }, ] satisfies DataTableColumnDef[]; }, [team, onMoveDocument]); const onPaginationChange = (page: number, perPage: number) => { startTransition(() => { updateSearchParams({ page, perPage, }); }); }; const results = data ?? { data: [], perPage: 10, currentPage: 1, totalPages: 1, }; return (
), }} > {(table) => }
{isPending && (
)}
); } type DataTableTitleProps = { row: DocumentsTableRow; teamUrl?: string; }; export const DataTableTitle = ({ row, teamUrl }: DataTableTitleProps) => { const { user } = useSession(); const recipient = row.recipients.find((recipient) => recipient.email === user.email); const isOwner = row.user.id === user.id; const isRecipient = !!recipient; const isCurrentTeamDocument = teamUrl && row.team?.url === teamUrl; const documentsPath = formatDocumentsPath(isCurrentTeamDocument ? teamUrl : undefined); const formatPath = row.folderId ? `${documentsPath}/f/${row.folderId}/${row.id}` : `${documentsPath}/${row.id}`; return match({ isOwner, isRecipient, isCurrentTeamDocument, }) .with({ isOwner: true }, { isCurrentTeamDocument: true }, () => ( {row.title} )) .with({ isRecipient: true }, () => ( {row.title} )) .otherwise(() => ( {row.title} )); };