feat(DataTable): Implement new DataTable component with hooks and optimized features

- Added DataTable component with support for virtual scrolling, row selection, and customizable columns.
- Introduced hooks for debouncing search input, managing row selection, and calculating column styles.
- Enhanced accessibility with keyboard navigation and selection checkboxes.
- Implemented skeleton loading state for better user experience during data fetching.
- Added DataTableSearch component for filtering data with debounced input.
- Created utility logger for improved debugging in development.
- Updated translations to support new UI elements and actions.
This commit is contained in:
Marco Beretta 2025-09-23 23:30:27 +02:00
parent ecadc2ec88
commit 76b34775f0
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
14 changed files with 1215 additions and 3294 deletions

View file

@ -1,6 +1,7 @@
import { useCallback, useState, useMemo } from 'react';
import { useCallback, useState, useMemo, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { TrashIcon, MessageSquare } from 'lucide-react';
import type { ColumnDef, SortingState } from '@tanstack/react-table';
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
import {
OGDialog,
@ -19,8 +20,8 @@ import {
} from '@librechat/client';
import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider';
import { NotificationSeverity } from '~/common';
import { formatDate, cn } from '~/utils';
import { useLocalize } from '~/hooks';
import { formatDate } from '~/utils';
const DEFAULT_PARAMS: SharedLinksListParams = {
pageSize: 25,
@ -30,14 +31,35 @@ const DEFAULT_PARAMS: SharedLinksListParams = {
search: '',
};
type SortKey = 'createdAt' | 'title';
const isSortKey = (v: string): v is SortKey => v === 'createdAt' || v === 'title';
const defaultSort: SortingState = [
{
id: 'createdAt',
desc: true,
},
];
type TableColumn<TData, TValue> = ColumnDef<TData, TValue> & {
meta?: {
className?: string;
hideOnMobile?: boolean;
};
};
export default function SharedLinks() {
const localize = useLocalize();
const { showToast } = useToastContext();
const isSmallScreen = useMediaQuery('(max-width: 768px)');
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
const [sorting, setSorting] = useState<SortingState>(defaultSort);
const [searchValue, setSearchValue] = useState('');
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isFetching, refetch, isLoading } =
useSharedLinksQuery(queryParams, {
@ -48,38 +70,115 @@ export default function SharedLinks() {
refetchOnMount: false,
});
const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => {
const [allKnownLinks, setAllKnownLinks] = useState<SharedLinkItem[]>([]);
const handleSearchChange = useCallback((value: string) => {
const trimmedValue = value.trim();
setSearchValue(trimmedValue);
setAllKnownLinks([]);
setQueryParams((prev) => ({
...prev,
sortBy: sortField as 'title' | 'createdAt',
sortDirection: sortOrder,
search: trimmedValue,
}));
}, []);
const handleFilterChange = useCallback((value: string) => {
const encodedValue = encodeURIComponent(value.trim());
setQueryParams((prev) => ({
...prev,
search: encodedValue,
}));
}, []);
const handleSortingChange = useCallback(
(updater: SortingState | ((old: SortingState) => SortingState)) => {
setSorting((prev) => {
const next = typeof updater === 'function' ? updater(prev) : updater;
const allLinks = useMemo(() => {
if (!data?.pages) {
return [];
const coerced = next;
const primary = coerced[0];
// Seed allKnown with current data before changing params
if (data?.pages) {
const currentFlattened = data.pages.flatMap((page) => page?.links?.filter(Boolean) ?? []);
setAllKnownLinks(currentFlattened);
}
setQueryParams((p) => {
let sortBy: SortKey;
let sortDirection: 'asc' | 'desc';
if (primary && isSortKey(primary.id)) {
sortBy = primary.id;
sortDirection = primary.desc ? 'desc' : 'asc';
} else {
sortBy = 'createdAt';
sortDirection = 'desc';
}
const newParams = {
...p,
sortBy,
sortDirection,
};
return newParams;
});
return coerced;
});
},
[setQueryParams, data?.pages],
);
const handleError = useCallback(
(error: Error) => {
console.error('DataTable error:', error);
showToast({
message: localize('com_ui_share_error'),
severity: NotificationSeverity.ERROR,
});
},
[showToast, localize],
);
useEffect(() => {
if (!data?.pages) return;
const newFlattened = data.pages.flatMap((page) => page?.links?.filter(Boolean) ?? []);
const toAdd = newFlattened.filter(
(link: SharedLinkItem) => !allKnownLinks.some((known) => known.shareId === link.shareId),
);
if (toAdd.length > 0) {
setAllKnownLinks((prev) => [...prev, ...toAdd]);
}
return data.pages.flatMap((page) => page.links.filter(Boolean));
}, [data?.pages]);
const displayData = useMemo(() => {
const primary = sorting[0];
if (!primary || allKnownLinks.length === 0) return allKnownLinks;
return [...allKnownLinks].sort((a: SharedLinkItem, b: SharedLinkItem) => {
let compare: number;
if (primary.id === 'createdAt') {
const aDate = new Date(a.createdAt || 0);
const bDate = new Date(b.createdAt || 0);
compare = aDate.getTime() - bDate.getTime();
} else if (primary.id === 'title') {
compare = (a.title || '').localeCompare(b.title || '');
} else {
return 0;
}
return primary.desc ? -compare : compare;
});
}, [allKnownLinks, sorting]);
const deleteMutation = useDeleteSharedLinkMutation({
onSuccess: async () => {
onSuccess: (data, variables) => {
const { shareId } = variables;
setAllKnownLinks((prev) => prev.filter((link) => link.shareId !== shareId));
showToast({
message: localize('com_ui_shared_link_delete_success'),
severity: NotificationSeverity.SUCCESS,
});
setIsDeleteOpen(false);
setDeleteRow(null);
await refetch();
refetch();
},
onError: (error) => {
console.error('Delete error:', error);
onError: () => {
showToast({
message: localize('com_ui_share_delete_error'),
severity: NotificationSeverity.ERROR,
@ -87,78 +186,47 @@ export default function SharedLinks() {
},
});
const handleDelete = useCallback(
async (selectedRows: SharedLinkItem[]) => {
const validRows = selectedRows.filter(
(row) => typeof row.shareId === 'string' && row.shareId.length > 0,
);
if (validRows.length === 0) {
return;
}
try {
for (const row of validRows) {
await deleteMutation.mutateAsync({ shareId: row.shareId });
}
showToast({
message: localize(
validRows.length === 1
? 'com_ui_shared_link_delete_success'
: 'com_ui_shared_link_bulk_delete_success',
),
severity: NotificationSeverity.SUCCESS,
});
} catch (error) {
console.error('Failed to delete shared links:', error);
showToast({
message: localize('com_ui_share_delete_error'),
severity: NotificationSeverity.ERROR,
});
}
},
[deleteMutation, showToast, localize],
);
const handleFetchNextPage = useCallback(async () => {
if (hasNextPage !== true || isFetchingNextPage) {
return;
}
if (!hasNextPage || isFetchingNextPage) return;
await fetchNextPage();
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
try {
await fetchNextPage();
} catch (error) {
console.error('Failed to fetch next page:', error);
showToast({
message: localize('com_ui_share_delete_error'),
severity: NotificationSeverity.ERROR,
});
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage, showToast, localize]);
const effectiveIsLoading = isLoading && displayData.length === 0;
const effectiveIsFetching = isFetchingNextPage;
const confirmDelete = useCallback(() => {
if (deleteRow) {
handleDelete([deleteRow]);
if (!deleteRow?.shareId) {
showToast({
message: localize('com_ui_share_delete_error'),
severity: NotificationSeverity.WARNING,
});
return;
}
setIsDeleteOpen(false);
}, [deleteRow, handleDelete]);
deleteMutation.mutate({ shareId: deleteRow.shareId });
}, [deleteMutation, deleteRow, localize, showToast]);
const columns = useMemo(
const columns: TableColumn<Record<string, unknown>, unknown>[] = useMemo(
() => [
{
accessorKey: 'title',
header: () => <span className="text-xs sm:text-sm">{localize('com_ui_name')}</span>,
accessorFn: (row: Record<string, unknown>): unknown => {
const link = row as SharedLinkItem;
return link.title;
},
header: () => (
<span className="text-xs text-text-primary sm:text-sm">{localize('com_ui_name')}</span>
),
cell: ({ row }) => {
const { title, shareId } = row.original;
const link = row.original as SharedLinkItem;
const { title, shareId } = link;
return (
<div className="flex items-center gap-2">
<Link
to={`/share/${shareId}`}
target="_blank"
rel="noopener noreferrer"
className="block truncate text-blue-500 hover:underline"
title={title}
className="flex items-center truncate text-blue-500 hover:underline"
aria-label={localize('com_ui_open_link', { 0: title })}
>
{title}
</Link>
@ -166,64 +234,81 @@ export default function SharedLinks() {
);
},
meta: {
size: '35%',
mobileSize: '50%',
enableSorting: true,
className: 'min-w-[150px] flex-1',
},
enableSorting: true,
},
{
accessorKey: 'createdAt',
header: () => <span className="text-xs sm:text-sm">{localize('com_ui_date')}</span>,
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
meta: {
size: '10%',
mobileSize: '20%',
enableSorting: true,
accessorFn: (row: Record<string, unknown>): unknown => {
const link = row as SharedLinkItem;
return link.createdAt;
},
header: () => (
<span className="text-xs text-text-primary sm:text-sm">{localize('com_ui_date')}</span>
),
cell: ({ row }) => {
const link = row.original as SharedLinkItem;
return formatDate(link.createdAt?.toString() ?? '', isSmallScreen);
},
meta: {
className: 'w-32 sm:w-40',
hideOnMobile: true,
},
enableSorting: true,
},
{
accessorKey: 'actions',
header: () => <Label>{localize('com_assistants_actions')}</Label>,
meta: {
size: '7%',
mobileSize: '25%',
enableSorting: false,
},
cell: ({ row }) => (
<div className="flex items-center gap-2">
<TooltipAnchor
description={localize('com_ui_view_source')}
render={
<Button
variant="ghost"
className="h-8 w-8 p-0 hover:bg-surface-hover"
onClick={() => {
window.open(`/c/${row.original.conversationId}`, '_blank');
}}
title={localize('com_ui_view_source')}
>
<MessageSquare className="size-4" />
</Button>
}
/>
<TooltipAnchor
description={localize('com_ui_delete')}
render={
<Button
variant="ghost"
className="h-8 w-8 p-0 hover:bg-surface-hover"
onClick={() => {
setDeleteRow(row.original);
setIsDeleteOpen(true);
}}
title={localize('com_ui_delete')}
>
<TrashIcon className="size-4" />
</Button>
}
/>
</div>
id: 'actions',
accessorFn: (row: Record<string, unknown>): unknown => null,
header: () => (
<span className="text-xs text-text-primary sm:text-sm">
{localize('com_assistants_actions')}
</span>
),
cell: ({ row }) => {
const link = row.original as SharedLinkItem;
const { title, conversationId, shareId } = link;
return (
<div className="flex items-center gap-2">
<TooltipAnchor
description={localize('com_ui_view_source')}
render={
<Button
variant="ghost"
className="h-8 w-8 p-0 hover:bg-surface-hover"
onClick={() => {
window.open(`/c/${conversationId}`, '_blank');
}}
aria-label={localize('com_ui_view_source_conversation', { 0: title })}
>
<MessageSquare className="size-4" />
</Button>
}
/>
<TooltipAnchor
description={localize('com_ui_delete')}
render={
<Button
variant="destructive"
className="h-8 w-8 p-0"
onClick={() => {
setDeleteRow(link);
setIsDeleteOpen(true);
}}
aria-label={localize('com_ui_delete_link_title', { 0: title })}
>
<TrashIcon className="size-4" />
</Button>
}
/>
</div>
);
},
meta: {
className: 'w-24',
},
enableSorting: false,
},
],
[isSmallScreen, localize],
@ -233,40 +318,40 @@ export default function SharedLinks() {
<div className="flex items-center justify-between">
<Label id="shared-links-label">{localize('com_nav_shared_links')}</Label>
<OGDialog open={isOpen} onOpenChange={setIsOpen}>
<OGDialogTrigger asChild onClick={() => setIsOpen(true)}>
<OGDialogTrigger asChild>
<Button aria-labelledby="shared-links-label" variant="outline">
{localize('com_ui_manage')}
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-11/12 max-w-5xl">
<OGDialogContent className={cn('w-11/12 max-w-6xl', isSmallScreen && 'px-0 pb-0')}>
<OGDialogHeader>
<OGDialogTitle>{localize('com_nav_shared_links')}</OGDialogTitle>
</OGDialogHeader>
<DataTable
columns={columns}
data={allLinks}
onDelete={handleDelete}
data={displayData}
isLoading={effectiveIsLoading}
isFetching={effectiveIsFetching}
config={{
skeleton: { count: 10 },
skeleton: { count: 11 },
search: {
filterColumn: 'title',
enableSearch: true,
debounce: 300,
},
selection: {
enableRowSelection: true,
showCheckboxes: true,
enableRowSelection: false,
showCheckboxes: false,
},
}}
filterValue={searchValue}
onFilterChange={handleSearchChange}
fetchNextPage={handleFetchNextPage}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
isFetching={isFetching}
fetchNextPage={handleFetchNextPage}
onFilterChange={handleFilterChange}
isLoading={isLoading}
onSortingChange={handleSort}
sortBy={queryParams.sortBy}
sortDirection={queryParams.sortDirection}
sorting={sorting}
onSortingChange={handleSortingChange}
onError={handleError}
/>
</OGDialogContent>
</OGDialog>
@ -276,15 +361,13 @@ export default function SharedLinks() {
title={localize('com_ui_delete_shared_link')}
className="w-11/12 max-w-md"
main={
<>
<div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full items-center gap-2">
<Label htmlFor="dialog-confirm-delete" className="text-left text-sm font-medium">
{localize('com_ui_delete_confirm')} <strong>{deleteRow?.title}</strong>
</Label>
</div>
<div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full items-center gap-2">
<Label className="text-left text-sm font-medium">
{localize('com_ui_delete_confirm')} <strong>{deleteRow?.title}</strong>
</Label>
</div>
</>
</div>
}
selection={{
selectHandler: confirmDelete,

View file

@ -1,4 +1,4 @@
import { useState, useCallback, useMemo } from 'react';
import { useState, useCallback, useMemo, useEffect } from 'react';
import { TrashIcon, ArchiveRestore } from 'lucide-react';
import type { ColumnDef, SortingState } from '@tanstack/react-table';
import {
@ -14,6 +14,7 @@ import {
Spinner,
useToastContext,
useMediaQuery,
DataTable,
} from '@librechat/client';
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
import {
@ -23,9 +24,8 @@ import {
} from '~/data-provider';
import { MinimalIcon } from '~/components/Endpoints';
import { NotificationSeverity } from '~/common';
import { formatDate, cn } from '~/utils';
import { useLocalize } from '~/hooks';
import { formatDate } from '~/utils';
import DataTable from './DataTable';
const DEFAULT_PARAMS = {
isArchived: true,
@ -76,9 +76,12 @@ export default function ArchivedChatsTable() {
refetchOnMount: false,
});
const [allKnownConversations, setAllKnownConversations] = useState<TConversation[]>([]);
const handleSearchChange = useCallback((value: string) => {
const trimmedValue = value.trim();
setSearchValue(trimmedValue);
setAllKnownConversations([]);
setQueryParams((prev) => ({
...prev,
search: trimmedValue,
@ -93,25 +96,31 @@ export default function ArchivedChatsTable() {
const coerced = next;
const primary = coerced[0];
setQueryParams((p) => {
const newParams = (() => {
if (primary && isSortKey(primary.id)) {
return {
...p,
sortBy: primary.id,
sortDirection: primary.desc ? 'desc' : 'asc',
};
}
return {
...p,
sortBy: 'createdAt',
sortDirection: 'desc',
};
})();
// Seed allKnown with current data before changing params
if (data?.pages) {
const currentFlattened = data.pages.flatMap(
(page) => page?.conversations?.filter(Boolean) ?? [],
);
setAllKnownConversations(currentFlattened);
}
setTimeout(() => {
refetch();
}, 0);
setQueryParams((p) => {
let sortBy: SortKey;
let sortDirection: 'asc' | 'desc';
if (primary && isSortKey(primary.id)) {
sortBy = primary.id;
sortDirection = primary.desc ? 'desc' : 'asc';
} else {
sortBy = 'createdAt';
sortDirection = 'desc';
}
const newParams = {
...p,
sortBy,
sortDirection,
};
return newParams;
});
@ -119,7 +128,7 @@ export default function ArchivedChatsTable() {
return coerced;
});
},
[setQueryParams, setSorting, refetch],
[setQueryParams, data?.pages],
);
const handleError = useCallback(
@ -133,14 +142,45 @@ export default function ArchivedChatsTable() {
[showToast, localize],
);
const allConversations = useMemo(() => {
if (!data?.pages) return [];
return data.pages.flatMap((page) => page?.conversations?.filter(Boolean) ?? []);
useEffect(() => {
if (!data?.pages) return;
const newFlattened = data.pages.flatMap((page) => page?.conversations?.filter(Boolean) ?? []);
const toAdd = newFlattened.filter(
(convo: TConversation) =>
!allKnownConversations.some((known) => known.conversationId === convo.conversationId),
);
if (toAdd.length > 0) {
setAllKnownConversations((prev) => [...prev, ...toAdd]);
}
}, [data?.pages]);
const displayData = useMemo(() => {
const primary = sorting[0];
if (!primary || allKnownConversations.length === 0) return allKnownConversations;
return [...allKnownConversations].sort((a: TConversation, b: TConversation) => {
let compare: number;
if (primary.id === 'createdAt') {
const aDate = new Date(a.createdAt || 0);
const bDate = new Date(b.createdAt || 0);
compare = aDate.getTime() - bDate.getTime();
} else if (primary.id === 'title') {
compare = (a.title || '').localeCompare(b.title || '');
} else {
return 0;
}
return primary.desc ? -compare : compare;
});
}, [allKnownConversations, sorting]);
const unarchiveMutation = useArchiveConvoMutation({
onSuccess: async () => {
await refetch();
onSuccess: (data, variables) => {
const { conversationId } = variables;
setAllKnownConversations((prev) => prev.filter((c) => c.conversationId !== conversationId));
refetch();
},
onError: () => {
showToast({
@ -151,13 +191,15 @@ export default function ArchivedChatsTable() {
});
const deleteMutation = useDeleteConversationMutation({
onSuccess: async () => {
onSuccess: (data, variables) => {
const { conversationId } = variables;
setAllKnownConversations((prev) => prev.filter((c) => c.conversationId !== conversationId));
showToast({
message: localize('com_ui_archived_conversation_delete_success'),
severity: NotificationSeverity.SUCCESS,
});
setIsDeleteOpen(false);
await refetch();
refetch();
},
onError: () => {
showToast({
@ -172,6 +214,9 @@ export default function ArchivedChatsTable() {
await fetchNextPage();
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
const effectiveIsLoading = isLoading && displayData.length === 0;
const effectiveIsFetching = isFetchingNextPage;
const confirmDelete = useCallback(() => {
if (!deleteRow?.conversationId) {
showToast({
@ -183,19 +228,37 @@ export default function ArchivedChatsTable() {
deleteMutation.mutate({ conversationId: deleteRow.conversationId });
}, [deleteMutation, deleteRow, localize, showToast]);
const columns: TableColumn<TConversation, any>[] = useMemo(
const handleUnarchive = useCallback(
(conversationId: string) => {
setUnarchivingId(conversationId);
unarchiveMutation.mutate(
{ conversationId, isArchived: false },
{ onSettled: () => setUnarchivingId(null) },
);
},
[unarchiveMutation],
);
const columns: TableColumn<Record<string, unknown>, unknown>[] = useMemo(
() => [
{
accessorKey: 'title',
accessorFn: (row: Record<string, unknown>): unknown => {
const convo = row as TConversation;
return convo.title;
},
header: () => (
<span className="text-xs sm:text-sm">{localize('com_nav_archive_name')}</span>
<span className="text-xs text-text-primary sm:text-sm">
{localize('com_nav_archive_name')}
</span>
),
cell: ({ row }) => {
const { conversationId, title } = row.original;
const convo = row.original as TConversation;
const { conversationId, title } = convo;
return (
<div className="flex items-center gap-2">
<MinimalIcon
endpoint={row.original.endpoint}
endpoint={convo.endpoint}
size={28}
isCreatedByUser={false}
iconClassName="size-4"
@ -214,34 +277,43 @@ export default function ArchivedChatsTable() {
);
},
meta: {
priority: 3,
minWidth: 'min-content',
className: 'min-w-[150px] flex-1',
},
enableSorting: true,
},
{
accessorKey: 'createdAt',
accessorFn: (row: Record<string, unknown>): unknown => {
const convo = row as TConversation;
return convo.createdAt;
},
header: () => (
<span className="text-xs sm:text-sm">{localize('com_nav_archive_created_at')}</span>
<span className="text-xs text-text-primary sm:text-sm">
{localize('com_nav_archive_created_at')}
</span>
),
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
cell: ({ row }) => {
const convo = row.original as TConversation;
return formatDate(convo.createdAt?.toString() ?? '', isSmallScreen);
},
meta: {
priority: 2,
minWidth: '80px',
className: 'w-32 sm:w-40',
hideOnMobile: true,
},
enableSorting: true,
},
{
id: 'actions',
accessorFn: (row: Record<string, unknown>): unknown => null,
header: () => (
<Label className="px-2 py-0 text-xs sm:px-2 sm:py-2 sm:text-sm">
<span className="text-xs text-text-primary sm:text-sm">
{localize('com_assistants_actions')}
</Label>
</span>
),
cell: ({ row }) => {
const conversation = row.original;
const { title } = conversation;
const isRowUnarchiving = unarchivingId === conversation.conversationId;
const convo = row.original as TConversation;
const { title } = convo;
const isRowUnarchiving = unarchivingId === convo.conversationId;
return (
<div className="flex items-center gap-2">
@ -252,13 +324,9 @@ export default function ArchivedChatsTable() {
variant="ghost"
className="h-8 w-8 p-0 hover:bg-surface-hover"
onClick={() => {
const conversationId = conversation.conversationId;
const conversationId = convo.conversationId;
if (!conversationId) return;
setUnarchivingId(conversationId);
unarchiveMutation.mutate(
{ conversationId, isArchived: false },
{ onSettled: () => setUnarchivingId(null) },
);
handleUnarchive(conversationId);
}}
disabled={isRowUnarchiving}
aria-label={localize('com_ui_unarchive_conversation_title', { 0: title })}
@ -272,9 +340,9 @@ export default function ArchivedChatsTable() {
render={
<Button
variant="destructive"
className="h-8 w-8 p-0 hover:bg-surface-hover"
className="h-8 w-8 p-0"
onClick={() => {
setDeleteRow(row.original);
setDeleteRow(convo);
setIsDeleteOpen(true);
}}
aria-label={localize('com_ui_delete_conversation_title', { 0: title })}
@ -287,13 +355,12 @@ export default function ArchivedChatsTable() {
);
},
meta: {
priority: 1,
minWidth: '120px',
className: 'w-24',
},
enableSorting: false,
},
],
[isSmallScreen, localize, unarchiveMutation, unarchivingId],
[isSmallScreen, localize, handleUnarchive, unarchivingId],
);
return (
@ -305,17 +372,17 @@ export default function ArchivedChatsTable() {
{localize('com_ui_manage')}
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-11/12 max-w-5xl">
<OGDialogContent className={cn('w-11/12 max-w-6xl', isSmallScreen && 'px-0 pb-0')}>
<OGDialogHeader>
<OGDialogTitle>{localize('com_nav_archived_chats')}</OGDialogTitle>
</OGDialogHeader>
<DataTable
columns={columns}
data={allConversations}
isLoading={isLoading}
isFetching={isFetching}
data={displayData}
isLoading={effectiveIsLoading}
isFetching={effectiveIsFetching}
config={{
skeleton: { count: 10 },
skeleton: { count: 11 },
search: {
filterColumn: 'title',
enableSearch: true,