mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
fix: table empty state and use the table somewhere else
This commit is contained in:
@ -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.`,
|
message: msg`There are no active drafts at the current moment. You can upload a document to start drafting.`,
|
||||||
icon: CheckCircle2,
|
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, () => ({
|
.with(ExtendedDocumentStatus.ALL, () => ({
|
||||||
title: msg`We're all empty`,
|
title: msg`We're all empty`,
|
||||||
message: msg`You have not yet created or received any documents. To create a document please upload one.`,
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4"
|
className="text-muted-foreground/60 mt-12 flex h-60 flex-col items-center justify-center gap-y-4"
|
||||||
data-testid="empty-document-state"
|
data-testid="empty-document-state"
|
||||||
>
|
>
|
||||||
<Icon className="h-12 w-12" strokeWidth={1.5} />
|
<Icon className="h-12 w-12" strokeWidth={1.5} />
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda
|
|||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
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 { TFindDocumentsInternalResponse } from '@documenso/trpc/server/document-router/schema';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
|
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 { DocumentsTableActionButton } from '../documents-table-action-button';
|
||||||
import { DocumentsTableActionDropdown } from '../documents-table-action-dropdown';
|
import { DocumentsTableActionDropdown } from '../documents-table-action-dropdown';
|
||||||
|
import { DocumentsTableEmptyState } from '../documents-table-empty-state';
|
||||||
|
|
||||||
export type DataTableProps = {
|
export type DataTableProps = {
|
||||||
data?: TFindDocumentsInternalResponse;
|
data?: TFindDocumentsInternalResponse;
|
||||||
@ -164,6 +166,13 @@ export function DocumentsDataTable({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getEmptyStateStatus = (): ExtendedDocumentStatus => {
|
||||||
|
if (selectedStatusValues.length > 0) {
|
||||||
|
return selectedStatusValues[0] as ExtendedDocumentStatus;
|
||||||
|
}
|
||||||
|
return ExtendedDocumentStatus.ALL;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<DataTable
|
<DataTable
|
||||||
@ -212,6 +221,10 @@ export function DocumentsDataTable({
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
emptyState={{
|
||||||
|
enable: !isLoading && !isLoadingError,
|
||||||
|
component: <DocumentsTableEmptyState status={getEmptyStateStatus()} />,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{(table) => <DataTablePagination additionalInformation="VisibleCount" table={table} />}
|
{(table) => <DataTablePagination additionalInformation="VisibleCount" table={table} />}
|
||||||
</DataTable>
|
</DataTable>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { FolderType } from '@documenso/lib/types/folder-type';
|
|||||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||||
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
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 { trpc } from '@documenso/trpc/react';
|
||||||
import { 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 { 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 { DocumentDropZoneWrapper } from '~/components/general/document/document-drop-zone-wrapper';
|
||||||
import { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
import { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
||||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
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 { DocumentsDataTable } from '~/components/tables/documents-table/data-table';
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
import { appMetaTags } from '~/utils/meta';
|
import { appMetaTags } from '~/utils/meta';
|
||||||
@ -248,27 +247,15 @@ export default function DocumentsPage() {
|
|||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<div>
|
<div>
|
||||||
{data &&
|
<DocumentsDataTable
|
||||||
data.count === 0 &&
|
data={data}
|
||||||
(!foldersData?.folders.length || foldersData.folders.length === 0) ? (
|
isLoading={isLoading}
|
||||||
<DocumentsTableEmptyState
|
isLoadingError={isLoadingError}
|
||||||
status={
|
onMoveDocument={(documentId) => {
|
||||||
Array.isArray(findDocumentSearchParams.status)
|
setDocumentToMove(documentId);
|
||||||
? findDocumentSearchParams.status[0] || ExtendedDocumentStatus.ALL
|
setIsMovingDocument(true);
|
||||||
: findDocumentSearchParams.status || ExtendedDocumentStatus.ALL
|
}}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<DocumentsDataTable
|
|
||||||
data={data}
|
|
||||||
isLoading={isLoading}
|
|
||||||
isLoadingError={isLoadingError}
|
|
||||||
onMoveDocument={(documentId) => {
|
|
||||||
setDocumentToMove(documentId);
|
|
||||||
setIsMovingDocument(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,22 +3,18 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { FolderIcon, HomeIcon, Loader2 } from 'lucide-react';
|
import { FolderIcon, HomeIcon, Loader2 } from 'lucide-react';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router';
|
import { useNavigate, useParams, useSearchParams } from 'react-router';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||||
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
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 { trpc } from '@documenso/trpc/react';
|
||||||
import {
|
import { ZFindDocumentsInternalRequestSchema } from '@documenso/trpc/server/document-router/schema';
|
||||||
type TFindDocumentsInternalResponse,
|
|
||||||
ZFindDocumentsInternalRequestSchema,
|
|
||||||
} from '@documenso/trpc/server/document-router/schema';
|
|
||||||
import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
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 { DocumentMoveToFolderDialog } from '~/components/dialogs/document-move-to-folder-dialog';
|
||||||
import { CreateFolderDialog } from '~/components/dialogs/folder-create-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 { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
||||||
import { DocumentDropZoneWrapper } from '~/components/general/document/document-drop-zone-wrapper';
|
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 { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
||||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
import { FolderCard } from '~/components/general/folder/folder-card';
|
||||||
import { PeriodSelector } from '~/components/general/period-selector';
|
import { DocumentsDataTable } from '~/components/tables/documents-table/data-table';
|
||||||
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 { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
import { appMetaTags } from '~/utils/meta';
|
import { appMetaTags } from '~/utils/meta';
|
||||||
|
|
||||||
@ -42,13 +33,23 @@ export function meta() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ZSearchParamsSchema = ZFindDocumentsInternalRequestSchema.pick({
|
const ZSearchParamsSchema = ZFindDocumentsInternalRequestSchema.pick({
|
||||||
status: true,
|
|
||||||
period: true,
|
period: true,
|
||||||
page: true,
|
page: true,
|
||||||
perPage: true,
|
perPage: true,
|
||||||
query: true,
|
query: true,
|
||||||
}).extend({
|
}).extend({
|
||||||
senderIds: z.string().transform(parseToIntegerArray).optional().catch([]),
|
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() {
|
export default function DocumentsPage() {
|
||||||
@ -71,15 +72,6 @@ export default function DocumentsPage() {
|
|||||||
const { mutateAsync: pinFolder } = trpc.folder.pinFolder.useMutation();
|
const { mutateAsync: pinFolder } = trpc.folder.pinFolder.useMutation();
|
||||||
const { mutateAsync: unpinFolder } = trpc.folder.unpinFolder.useMutation();
|
const { mutateAsync: unpinFolder } = trpc.folder.unpinFolder.useMutation();
|
||||||
|
|
||||||
const [stats, setStats] = useState<TFindDocumentsInternalResponse['stats']>({
|
|
||||||
[ExtendedDocumentStatus.DRAFT]: 0,
|
|
||||||
[ExtendedDocumentStatus.PENDING]: 0,
|
|
||||||
[ExtendedDocumentStatus.COMPLETED]: 0,
|
|
||||||
[ExtendedDocumentStatus.REJECTED]: 0,
|
|
||||||
[ExtendedDocumentStatus.INBOX]: 0,
|
|
||||||
[ExtendedDocumentStatus.ALL]: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const findDocumentSearchParams = useMemo(
|
const findDocumentSearchParams = useMemo(
|
||||||
() => ZSearchParamsSchema.safeParse(Object.fromEntries(searchParams.entries())).data || {},
|
() => ZSearchParamsSchema.safeParse(Object.fromEntries(searchParams.entries())).data || {},
|
||||||
[searchParams],
|
[searchParams],
|
||||||
@ -97,6 +89,7 @@ export default function DocumentsPage() {
|
|||||||
isLoading: isFoldersLoading,
|
isLoading: isFoldersLoading,
|
||||||
refetch: refetchFolders,
|
refetch: refetchFolders,
|
||||||
} = trpc.folder.getFolders.useQuery({
|
} = trpc.folder.getFolders.useQuery({
|
||||||
|
type: FolderType.DOCUMENT,
|
||||||
parentId: folderId,
|
parentId: folderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,28 +98,6 @@ export default function DocumentsPage() {
|
|||||||
void refetchFolders();
|
void refetchFolders();
|
||||||
}, [team?.url]);
|
}, [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 navigateToFolder = (folderId?: string | null) => {
|
||||||
const documentsPath = formatDocumentsPath(team?.url);
|
const documentsPath = formatDocumentsPath(team?.url);
|
||||||
|
|
||||||
@ -255,65 +226,19 @@ export default function DocumentsPage() {
|
|||||||
<Trans>Documents</Trans>
|
<Trans>Documents</Trans>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="-m-1 flex flex-wrap gap-x-4 gap-y-6 overflow-hidden p-1">
|
|
||||||
<Tabs value={findDocumentSearchParams.status || 'ALL'} className="overflow-x-auto">
|
|
||||||
<TabsList>
|
|
||||||
{[
|
|
||||||
ExtendedDocumentStatus.INBOX,
|
|
||||||
ExtendedDocumentStatus.PENDING,
|
|
||||||
ExtendedDocumentStatus.COMPLETED,
|
|
||||||
ExtendedDocumentStatus.DRAFT,
|
|
||||||
ExtendedDocumentStatus.ALL,
|
|
||||||
].map((value) => (
|
|
||||||
<TabsTrigger
|
|
||||||
key={value}
|
|
||||||
className="hover:text-foreground min-w-[60px]"
|
|
||||||
value={value}
|
|
||||||
asChild
|
|
||||||
>
|
|
||||||
<Link to={getTabHref(value)} preventScrollReset>
|
|
||||||
<DocumentStatus status={value} />
|
|
||||||
|
|
||||||
{value !== ExtendedDocumentStatus.ALL && (
|
|
||||||
<span className="ml-1 inline-block opacity-50">{stats[value]}</span>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
</TabsTrigger>
|
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
{team && <DocumentsTableSenderFilter teamId={team.id} />}
|
|
||||||
|
|
||||||
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
|
||||||
<PeriodSelector />
|
|
||||||
</div>
|
|
||||||
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
|
||||||
<DocumentSearch initialValue={findDocumentSearchParams.query} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<div>
|
<div>
|
||||||
{data &&
|
<DocumentsDataTable
|
||||||
data.count === 0 &&
|
data={data}
|
||||||
(!foldersData?.folders.length || foldersData.folders.length === 0) ? (
|
isLoading={isLoading}
|
||||||
<DocumentsTableEmptyState
|
isLoadingError={isLoadingError}
|
||||||
status={findDocumentSearchParams.status || ExtendedDocumentStatus.ALL}
|
onMoveDocument={(documentId) => {
|
||||||
/>
|
setDocumentToMove(documentId);
|
||||||
) : (
|
setIsMovingDocument(true);
|
||||||
<DocumentsTable
|
}}
|
||||||
data={data}
|
/>
|
||||||
isLoading={isLoading}
|
|
||||||
isLoadingError={isLoadingError}
|
|
||||||
onMoveDocument={(documentId) => {
|
|
||||||
setDocumentToMove(documentId);
|
|
||||||
setIsMovingDocument(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,10 @@ interface DataTableProps<TData, TValue> {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
component?: React.ReactNode;
|
component?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
emptyState?: {
|
||||||
|
enable: boolean;
|
||||||
|
component?: React.ReactNode;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData, TValue>({
|
||||||
@ -72,6 +76,7 @@ export function DataTable<TData, TValue>({
|
|||||||
onResetFilters,
|
onResetFilters,
|
||||||
isStatusFiltered,
|
isStatusFiltered,
|
||||||
isTimePeriodFiltered,
|
isTimePeriodFiltered,
|
||||||
|
emptyState,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||||
@ -149,61 +154,65 @@ export function DataTable<TData, TValue>({
|
|||||||
isStatusFiltered={isStatusFiltered}
|
isStatusFiltered={isStatusFiltered}
|
||||||
isTimePeriodFiltered={isTimePeriodFiltered}
|
isTimePeriodFiltered={isTimePeriodFiltered}
|
||||||
/>
|
/>
|
||||||
<div className="rounded-md border">
|
{table.getRowModel().rows?.length || error?.enable || skeleton?.enable ? (
|
||||||
<Table>
|
<div className="rounded-md border">
|
||||||
<TableHeader>
|
<Table>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
<TableHeader>
|
||||||
<TableRow key={headerGroup.id}>
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
{headerGroup.headers.map((header) => {
|
<TableRow key={headerGroup.id}>
|
||||||
return (
|
{headerGroup.headers.map((header) => {
|
||||||
<TableHead key={header.id} colSpan={header.colSpan}>
|
return (
|
||||||
{header.isPlaceholder
|
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||||
? null
|
{header.isPlaceholder
|
||||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
? null
|
||||||
</TableHead>
|
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
);
|
</TableHead>
|
||||||
})}
|
);
|
||||||
</TableRow>
|
})}
|
||||||
))}
|
</TableRow>
|
||||||
</TableHeader>
|
))}
|
||||||
<TableBody>
|
</TableHeader>
|
||||||
{table.getRowModel().rows?.length ? (
|
<TableBody>
|
||||||
table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows?.length ? (
|
||||||
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
table.getRowModel().rows.map((row) => (
|
||||||
{row.getVisibleCells().map((cell) => (
|
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
||||||
<TableCell key={cell.id}>
|
{row.getVisibleCells().map((cell) => (
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : error?.enable ? (
|
||||||
|
<TableRow>
|
||||||
|
{error.component ?? (
|
||||||
|
<TableCell colSpan={columns.length} className="h-32 text-center">
|
||||||
|
<Trans>Something went wrong.</Trans>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
) : skeleton?.enable ? (
|
||||||
) : error?.enable ? (
|
Array.from({ length: skeleton.rows }).map((_, i) => (
|
||||||
<TableRow>
|
<TableRow key={`skeleton-row-${i}`}>
|
||||||
{error.component ?? (
|
{skeleton.component ?? <Skeleton />}
|
||||||
<TableCell colSpan={columns.length} className="h-32 text-center">
|
</TableRow>
|
||||||
<Trans>Something went wrong.</Trans>
|
))
|
||||||
</TableCell>
|
) : null}
|
||||||
)}
|
</TableBody>
|
||||||
</TableRow>
|
</Table>
|
||||||
) : skeleton?.enable ? (
|
</div>
|
||||||
Array.from({ length: skeleton.rows }).map((_, i) => (
|
) : emptyState?.enable ? (
|
||||||
<TableRow key={`skeleton-row-${i}`}>
|
(emptyState.component ?? (
|
||||||
{skeleton.component ?? <Skeleton />}
|
<div className="flex h-24 items-center justify-center text-center">No results.</div>
|
||||||
</TableRow>
|
))
|
||||||
))
|
) : (
|
||||||
) : (
|
<div className="flex h-24 items-center justify-center text-center">No results.</div>
|
||||||
<TableRow>
|
)}
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{children && <div className="mt-8 w-full">{children(table)}</div>}
|
{children && (table.getRowModel().rows?.length || error?.enable || skeleton?.enable) && (
|
||||||
|
<div className="mt-8 w-full">{children(table)}</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { CheckCircle2, Clock, File, Inbox, XCircle } from 'lucide-react';
|
import { CheckCircle2, Clock, File, Inbox, XCircle } from 'lucide-react';
|
||||||
|
|
||||||
export const statuses = [
|
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',
|
value: 'DRAFT',
|
||||||
label: 'Draft',
|
label: 'Draft',
|
||||||
@ -29,13 +36,6 @@ export const statuses = [
|
|||||||
color: 'text-red-700 dark:text-red-300',
|
color: 'text-red-700 dark:text-red-300',
|
||||||
bgColor: 'bg-red-100 dark:bg-red-100 text-red-500 dark:text-red-700',
|
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 = [
|
export const timePeriods = [
|
||||||
|
|||||||
@ -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 (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
|
||||||
<Avatar className="h-9 w-9">
|
|
||||||
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>SC</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
|
||||||
<DropdownMenuLabel className="font-normal">
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-sm font-medium leading-none">shadcn</p>
|
|
||||||
<p className="text-muted-foreground text-xs leading-none">m@example.com</p>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Profile
|
|
||||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Billing
|
|
||||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Settings
|
|
||||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>New Team</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Log out
|
|
||||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user