diff --git a/apps/remix/app/components/tables/admin-organisation-stats-table.tsx b/apps/remix/app/components/tables/admin-organisation-stats-table.tsx
new file mode 100644
index 000000000..97275e1c1
--- /dev/null
+++ b/apps/remix/app/components/tables/admin-organisation-stats-table.tsx
@@ -0,0 +1,188 @@
+import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
+import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
+import { currentMonthlyPeriod } from '@documenso/lib/universal/monthly-period';
+import { trpc } from '@documenso/trpc/react';
+import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
+import { DataTable } from '@documenso/ui/primitives/data-table';
+import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
+import { Skeleton } from '@documenso/ui/primitives/skeleton';
+import { TableCell } from '@documenso/ui/primitives/table';
+import { useLingui } from '@lingui/react/macro';
+import { ChevronDownIcon, ChevronsUpDownIcon, ChevronUpIcon } from 'lucide-react';
+import { useMemo } from 'react';
+import { Link, useSearchParams } from 'react-router';
+
+type OrderByColumn = 'documentCount' | 'emailCount' | 'apiCount' | 'totalCount';
+type OrderByDirection = 'asc' | 'desc';
+
+const parseOrderByColumn = (value: string | undefined): OrderByColumn | undefined => {
+ if (value === 'documentCount' || value === 'emailCount' || value === 'apiCount' || value === 'totalCount') {
+ return value;
+ }
+
+ return undefined;
+};
+
+const parseOrderByDirection = (value: string | undefined): OrderByDirection => {
+ return value === 'asc' ? 'asc' : 'desc';
+};
+
+export const AdminOrganisationStatsTable = () => {
+ const { t } = useLingui();
+
+ const [searchParams] = useSearchParams();
+ const updateSearchParams = useUpdateSearchParams();
+
+ const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
+
+ // Default to the current month.
+ const period = searchParams?.get('period') ?? currentMonthlyPeriod();
+ const claimId = searchParams?.get('claimId') || undefined;
+ const orderByColumn = parseOrderByColumn(searchParams?.get('orderByColumn') ?? undefined);
+ const orderByDirection = parseOrderByDirection(searchParams?.get('orderByDirection') ?? undefined);
+
+ const { data, isLoading, isLoadingError } = trpc.admin.organisation.findStats.useQuery({
+ query: parsedSearchParams.query,
+ page: parsedSearchParams.page,
+ perPage: parsedSearchParams.perPage,
+ period,
+ claimId,
+ orderByColumn,
+ orderByDirection,
+ });
+
+ const onPaginationChange = (page: number, perPage: number) => {
+ updateSearchParams({
+ page,
+ perPage,
+ });
+ };
+
+ const handleColumnSort = (column: OrderByColumn) => {
+ const nextDirection = orderByColumn === column && orderByDirection === 'desc' ? 'asc' : 'desc';
+
+ updateSearchParams({
+ orderByColumn: column,
+ orderByDirection: nextDirection,
+ page: 1,
+ });
+ };
+
+ const results = data ?? {
+ data: [],
+ perPage: 10,
+ currentPage: 1,
+ totalPages: 1,
+ };
+
+ const columns = useMemo(() => {
+ const sortableHeader = (label: string, column: OrderByColumn) => (
+
+ );
+
+ return [
+ {
+ header: t`Organisation`,
+ accessorKey: 'organisationName',
+ cell: ({ row }) => (
+
+ {row.original.organisationName}
+
+ ),
+ },
+ {
+ header: t`Claim`,
+ accessorKey: 'originalClaimId',
+ cell: ({ row }) => {row.original.originalClaimId ?? '—'},
+ },
+ {
+ header: t`Period`,
+ accessorKey: 'period',
+ cell: ({ row }) => row.original.period,
+ },
+ {
+ header: () => sortableHeader(t`Documents`, 'documentCount'),
+ accessorKey: 'documentCount',
+ cell: ({ row }) => row.original.documentCount,
+ },
+ {
+ header: () => sortableHeader(t`Emails`, 'emailCount'),
+ accessorKey: 'emailCount',
+ cell: ({ row }) => row.original.emailCount,
+ },
+ {
+ header: () => sortableHeader(t`API`, 'apiCount'),
+ accessorKey: 'apiCount',
+ cell: ({ row }) => row.original.apiCount,
+ },
+ {
+ header: () => sortableHeader(t`Total`, 'totalCount'),
+ accessorKey: 'totalCount',
+ cell: ({ row }) => {row.original.totalCount},
+ },
+ ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[];
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [t, orderByColumn, orderByDirection]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ }}
+ >
+ {(table) => }
+
+
+ );
+};
diff --git a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx
index af24ab0bf..af00d92cb 100644
--- a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx
+++ b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx
@@ -10,6 +10,7 @@ import {
BarChart3,
Building2Icon,
FileStack,
+ LineChartIcon,
MailIcon,
Settings,
Trophy,
@@ -153,6 +154,20 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
+
+