From de45a63c971709ef9dc0c23c0a3741c04dd4f0ff Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 5 Jun 2025 12:07:19 +0000 Subject: [PATCH] chore: minor changes --- .../components/general/period-selector.tsx | 70 -------- .../lib/server-only/document/get-stats.ts | 64 +------ packages/ui/primitives/data-table.tsx | 167 ++++++++++++++++++ 3 files changed, 176 insertions(+), 125 deletions(-) delete mode 100644 apps/remix/app/components/general/period-selector.tsx create mode 100644 packages/ui/primitives/data-table.tsx diff --git a/apps/remix/app/components/general/period-selector.tsx b/apps/remix/app/components/general/period-selector.tsx deleted file mode 100644 index 025925a92..000000000 --- a/apps/remix/app/components/general/period-selector.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useMemo } from 'react'; - -import { Trans } from '@lingui/react/macro'; -import { useLocation, useNavigate, useSearchParams } from 'react-router'; - -import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@documenso/ui/primitives/select'; - -const isPeriodSelectorValue = (value: unknown): value is PeriodSelectorValue => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return ['', '7d', '14d', '30d'].includes(value as string); -}; - -export const PeriodSelector = () => { - const { pathname } = useLocation(); - const [searchParams] = useSearchParams(); - - const navigate = useNavigate(); - - const period = useMemo(() => { - const p = searchParams?.get('period') ?? 'all'; - - return isPeriodSelectorValue(p) ? p : 'all'; - }, [searchParams]); - - const onPeriodChange = (newPeriod: string) => { - if (!pathname) { - return; - } - - const params = new URLSearchParams(searchParams?.toString()); - - params.set('period', newPeriod); - - if (newPeriod === '' || newPeriod === 'all') { - params.delete('period'); - } - - void navigate(`${pathname}?${params.toString()}`, { preventScrollReset: true }); - }; - - return ( - - ); -}; diff --git a/packages/lib/server-only/document/get-stats.ts b/packages/lib/server-only/document/get-stats.ts index ba858a9c7..7446eb51a 100644 --- a/packages/lib/server-only/document/get-stats.ts +++ b/packages/lib/server-only/document/get-stats.ts @@ -1,17 +1,17 @@ import type { Prisma, User } from '@prisma/client'; import { DocumentVisibility, SigningStatus, TeamMemberRole } from '@prisma/client'; -import { DateTime } from 'luxon'; import { match } from 'ts-pattern'; -import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents'; import { prisma } from '@documenso/prisma'; import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; +import type { TimePeriod } from '@documenso/ui/primitives/data-table/utils/time-filters'; +import { getDateRangeForPeriod } from '@documenso/ui/primitives/data-table/utils/time-filters'; export type GetStatsInput = { user: User; team?: Omit; - period?: PeriodSelectorValue; + period?: TimePeriod; search?: string; folderId?: string; }; @@ -26,60 +26,14 @@ export const getStats = async ({ let createdAt: Prisma.DocumentWhereInput['createdAt']; if (period && period !== 'all-time') { - const now = DateTime.now(); - let startDate: DateTime; - let endDate: DateTime; + const dateRange = getDateRangeForPeriod(period); - switch (period) { - case 'today': - startDate = now.startOf('day'); - endDate = now.endOf('day'); - break; - case 'yesterday': - startDate = now.minus({ days: 1 }).startOf('day'); - endDate = now.minus({ days: 1 }).endOf('day'); - break; - case 'this-week': - startDate = now.startOf('week'); - endDate = now.endOf('week'); - break; - case 'last-week': - startDate = now.minus({ weeks: 1 }).startOf('week'); - endDate = now.minus({ weeks: 1 }).endOf('week'); - break; - case 'this-month': - startDate = now.startOf('month'); - endDate = now.endOf('month'); - break; - case 'last-month': - startDate = now.minus({ months: 1 }).startOf('month'); - endDate = now.minus({ months: 1 }).endOf('month'); - break; - case 'this-quarter': - startDate = now.startOf('quarter'); - endDate = now.endOf('quarter'); - break; - case 'last-quarter': - startDate = now.minus({ quarters: 1 }).startOf('quarter'); - endDate = now.minus({ quarters: 1 }).endOf('quarter'); - break; - case 'this-year': - startDate = now.startOf('year'); - endDate = now.endOf('year'); - break; - case 'last-year': - startDate = now.minus({ years: 1 }).startOf('year'); - endDate = now.minus({ years: 1 }).endOf('year'); - break; - default: - startDate = now.startOf('day'); - endDate = now.endOf('day'); + if (dateRange) { + createdAt = { + gte: dateRange.start.toJSDate(), + lte: dateRange.end.toJSDate(), + }; } - - createdAt = { - gte: startDate.toJSDate(), - lte: endDate.toJSDate(), - }; } const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team diff --git a/packages/ui/primitives/data-table.tsx b/packages/ui/primitives/data-table.tsx new file mode 100644 index 000000000..d27dab0aa --- /dev/null +++ b/packages/ui/primitives/data-table.tsx @@ -0,0 +1,167 @@ +import React, { useMemo } from 'react'; + +import { Trans } from '@lingui/react/macro'; +import type { + ColumnDef, + PaginationState, + Table as TTable, + Updater, + VisibilityState, +} from '@tanstack/react-table'; +import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; + +import { Skeleton } from './skeleton'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table'; + +export type DataTableChildren = (_table: TTable) => React.ReactNode; + +export type { ColumnDef as DataTableColumnDef } from '@tanstack/react-table'; + +export interface DataTableProps { + columns: ColumnDef[]; + columnVisibility?: VisibilityState; + data: TData[]; + perPage?: number; + currentPage?: number; + totalPages?: number; + onPaginationChange?: (_page: number, _perPage: number) => void; + onClearFilters?: () => void; + hasFilters?: boolean; + children?: DataTableChildren; + skeleton?: { + enable: boolean; + rows: number; + component?: React.ReactNode; + }; + error?: { + enable: boolean; + component?: React.ReactNode; + }; +} + +export function DataTable({ + columns, + columnVisibility, + data, + error, + perPage, + currentPage, + totalPages, + skeleton, + hasFilters, + onClearFilters, + onPaginationChange, + children, +}: DataTableProps) { + const pagination = useMemo(() => { + if (currentPage !== undefined && perPage !== undefined) { + return { + pageIndex: currentPage - 1, + pageSize: perPage, + }; + } + + return { + pageIndex: 0, + pageSize: 0, + }; + }, [currentPage, perPage]); + + const manualPagination = Boolean(currentPage !== undefined && totalPages !== undefined); + + const onTablePaginationChange = (updater: Updater) => { + if (typeof updater === 'function') { + const newState = updater(pagination); + + onPaginationChange?.(newState.pageIndex + 1, newState.pageSize); + } else { + onPaginationChange?.(updater.pageIndex + 1, updater.pageSize); + } + }; + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + state: { + pagination: manualPagination ? pagination : undefined, + columnVisibility, + }, + manualPagination, + pageCount: totalPages, + onPaginationChange: onTablePaginationChange, + }); + + return ( + <> +
+ + + {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. + + )} + + ) : skeleton?.enable ? ( + Array.from({ length: skeleton.rows }).map((_, i) => ( + {skeleton.component ?? } + )) + ) : ( + + +

+ No results found +

+ + {hasFilters && onClearFilters !== undefined && ( + + )} +
+
+ )} +
+
+
+ + {children &&
{children(table)}
} + + ); +}