diff --git a/client/src/components/Chat/Input/Files/Table/Columns.tsx b/client/src/components/Chat/Input/Files/Table/Columns.tsx index fb0eccee67..3ea1a12b0f 100644 --- a/client/src/components/Chat/Input/Files/Table/Columns.tsx +++ b/client/src/components/Chat/Input/Files/Table/Columns.tsx @@ -61,16 +61,7 @@ export const columns: ColumnDef[] = [ accessorKey: 'filename', header: ({ column }) => { const localize = useLocalize(); - return ( - - ); + return ; }, cell: ({ row }) => { const file = row.original; @@ -100,16 +91,7 @@ export const columns: ColumnDef[] = [ accessorKey: 'updatedAt', header: ({ column }) => { const localize = useLocalize(); - return ( - - ); + return ; }, cell: ({ row }) => { const isSmallScreen = useMediaQuery('(max-width: 768px)'); @@ -197,16 +179,7 @@ export const columns: ColumnDef[] = [ accessorKey: 'bytes', header: ({ column }) => { const localize = useLocalize(); - return ( - - ); + return ; }, cell: ({ row }) => { const suffix = ' MB'; diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index ecea9c3d14..9b7da860ea 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState, useMemo, memo, lazy, Suspense, useRef } from 'react'; import { useRecoilValue } from 'recoil'; -import { useMediaQuery } from '@librechat/client'; +import { Skeleton, useMediaQuery } from '@librechat/client'; import { PermissionTypes, Permissions } from 'librechat-data-provider'; import type { ConversationListResponse } from 'librechat-data-provider'; import type { InfiniteQueryObserverResult } from '@tanstack/react-query'; @@ -158,13 +158,12 @@ const Nav = memo( const headerButtons = useMemo( () => ( <> - + }> {hasAccessToBookmarks && ( <> -
- + }> @@ -229,7 +228,7 @@ const Nav = memo( isSearchLoading={isSearchLoading} />
- + }> diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx index ae25223a9b..e2073c02e6 100644 --- a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx @@ -1,8 +1,8 @@ -import { useCallback, useState, useMemo, useEffect } from 'react'; +import { useCallback, useState, useMemo, useEffect, useRef } from 'react'; import debounce from 'lodash/debounce'; import { useRecoilValue } from 'recoil'; import { Link } from 'react-router-dom'; -import { TrashIcon, MessageSquare, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'; +import { TrashIcon, MessageSquare } from 'lucide-react'; import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider'; import { OGDialog, @@ -20,15 +20,13 @@ import { Label, } from '@librechat/client'; import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider'; -import { useLocalize } from '~/hooks'; import { NotificationSeverity } from '~/common'; +import { useLocalize } from '~/hooks'; import { formatDate } from '~/utils'; import store from '~/store'; -const PAGE_SIZE = 25; - const DEFAULT_PARAMS: SharedLinksListParams = { - pageSize: PAGE_SIZE, + pageSize: 25, isPublic: true, sortBy: 'createdAt', sortDirection: 'desc', @@ -44,16 +42,33 @@ export default function SharedLinks() { const [deleteRow, setDeleteRow] = useState(null); const [isDeleteOpen, setIsDeleteOpen] = useState(false); const [isOpen, setIsOpen] = useState(false); + const prevSortRef = useRef({ + sortBy: DEFAULT_PARAMS.sortBy, + sortDirection: DEFAULT_PARAMS.sortDirection, + }); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } = useSharedLinksQuery(queryParams, { enabled: isOpen, - staleTime: 0, + staleTime: 30 * 1000, cacheTime: 5 * 60 * 1000, refetchOnWindowFocus: false, refetchOnMount: false, + keepPreviousData: false, }); + useEffect(() => { + if (!isOpen) return; + + const { sortBy, sortDirection } = queryParams; + const prevSort = prevSortRef.current; + + if (sortBy !== prevSort.sortBy || sortDirection !== prevSort.sortDirection) { + refetch(); + prevSortRef.current = { sortBy, sortDirection }; + } + }, [queryParams, isOpen, refetch]); + const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => { setQueryParams((prev) => ({ ...prev, @@ -71,7 +86,7 @@ export default function SharedLinks() { }, []); const debouncedFilterChange = useMemo( - () => debounce(handleFilterChange, 300), + () => debounce(handleFilterChange, 500), // Increased debounce time to 500ms [handleFilterChange], ); @@ -134,7 +149,7 @@ export default function SharedLinks() { } catch (error) { console.error('Failed to delete shared links:', error); showToast({ - message: localize('com_ui_bulk_delete_error'), + message: localize('com_ui_share_delete_error'), severity: NotificationSeverity.ERROR, }); } @@ -146,8 +161,17 @@ export default function SharedLinks() { if (hasNextPage !== true || 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 confirmDelete = useCallback(() => { if (deleteRow) { @@ -160,28 +184,7 @@ export default function SharedLinks() { () => [ { accessorKey: 'title', - header: () => { - const isSorted = queryParams.sortBy === 'title'; - const sortDirection = queryParams.sortDirection; - return ( - - ); - }, + header: () => {localize('com_ui_name')}, cell: ({ row }) => { const { title, shareId } = row.original; return ( @@ -201,36 +204,17 @@ export default function SharedLinks() { meta: { size: '35%', mobileSize: '50%', + enableSorting: true, }, }, { accessorKey: 'createdAt', - header: () => { - const isSorted = queryParams.sortBy === 'createdAt'; - const sortDirection = queryParams.sortDirection; - return ( - - ); - }, + header: () => {localize('com_ui_date')}, cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), meta: { size: '10%', mobileSize: '20%', + enableSorting: true, }, }, { @@ -243,6 +227,7 @@ export default function SharedLinks() { meta: { size: '7%', mobileSize: '25%', + enableSorting: false, }, cell: ({ row }) => (
@@ -281,24 +266,19 @@ export default function SharedLinks() { ), }, ], - [isSmallScreen, localize, queryParams, handleSort], + [isSmallScreen, localize], ); return (
- setIsOpen(true)}> - - + {localize('com_nav_shared_links')} @@ -314,7 +294,10 @@ export default function SharedLinks() { onFilterChange={debouncedFilterChange} filterValue={queryParams.search} isLoading={isLoading} - enableSearch={isSearchEnabled} + enableSearch={!!isSearchEnabled} + onSortChange={handleSort} + sortBy={queryParams.sortBy} + sortDirection={queryParams.sortDirection} /> @@ -322,7 +305,7 @@ export default function SharedLinks() {
diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx index 2e6f6df07d..588b7c0712 100644 --- a/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx +++ b/client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx @@ -1,11 +1,275 @@ -import { useState } from 'react'; -import { OGDialogTemplate, OGDialog, OGDialogTrigger, Button } from '@librechat/client'; -import ArchivedChatsTable from './ArchivedChatsTable'; +import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; +import debounce from 'lodash/debounce'; +import { useRecoilValue } from 'recoil'; +import { TrashIcon, ArchiveRestore } from 'lucide-react'; +import { + Button, + OGDialog, + OGDialogTrigger, + OGDialogTemplate, + OGDialogContent, + OGDialogHeader, + OGDialogTitle, + Label, + TooltipAnchor, + Spinner, + DataTable, + useToastContext, + useMediaQuery, +} from '@librechat/client'; +import type { ConversationListParams, TConversation } from 'librechat-data-provider'; +import { + useArchiveConvoMutation, + useConversationsInfiniteQuery, + useDeleteConversationMutation, +} from '~/data-provider'; +import { MinimalIcon } from '~/components/Endpoints'; +import { NotificationSeverity } from '~/common'; import { useLocalize } from '~/hooks'; +import { formatDate } from '~/utils'; +import store from '~/store'; -export default function ArchivedChats() { +const DEFAULT_PARAMS: ConversationListParams = { + isArchived: true, + sortBy: 'createdAt', + sortDirection: 'desc', + search: '', +}; + +type SortField = 'title' | 'createdAt'; + +export default function ArchivedChatsTable() { const localize = useLocalize(); + const isSmallScreen = useMediaQuery('(max-width: 768px)'); + const { showToast } = useToastContext(); + const isSearchEnabled = useRecoilValue(store.search); + const [isOpen, setIsOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const [deleteRow, setDeleteRow] = useState(null); + const [unarchivingId, setUnarchivingId] = useState(null); + const prevSortRef = useRef({ + sortBy: DEFAULT_PARAMS.sortBy, + sortDirection: DEFAULT_PARAMS.sortDirection, + }); + + const [queryParams, setQueryParams] = useState(DEFAULT_PARAMS); + const [searchInput, setSearchInput] = useState(''); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } = + useConversationsInfiniteQuery(queryParams, { + enabled: isOpen, + staleTime: 30 * 1000, + cacheTime: 5 * 60 * 1000, + refetchOnWindowFocus: false, + refetchOnMount: false, + keepPreviousData: false, + }); + + const handleSort = useCallback((field: string, direction: 'asc' | 'desc') => { + setQueryParams((prev) => ({ + ...prev, + sortBy: field as SortField, + sortDirection: direction, + })); + }, []); + + // Trigger refetch when sort parameters change + useEffect(() => { + if (!isOpen) return; // Only refetch if dialog is open + + const { sortBy, sortDirection } = queryParams; + const prevSort = prevSortRef.current; + + if (sortBy !== prevSort.sortBy || sortDirection !== prevSort.sortDirection) { + console.log('Sort changed, refetching...', { from: prevSort, to: { sortBy, sortDirection } }); + refetch(); + prevSortRef.current = { sortBy, sortDirection }; + } + }, [queryParams, isOpen, refetch]); + + const debouncedApplySearch = useMemo( + () => + debounce((value: string) => { + setQueryParams((prev) => ({ + ...prev, + search: encodeURIComponent(value.trim()), + })); + }, 500), // Increased debounce time to 500ms for better UX + [], + ); + + const onFilterChange = useCallback( + (value: string) => { + setSearchInput(value); + debouncedApplySearch(value); + }, + [debouncedApplySearch], + ); + + useEffect(() => { + return () => { + debouncedApplySearch.cancel(); + }; + }, [debouncedApplySearch]); + + const allConversations = useMemo(() => { + if (!data?.pages) return []; + return data.pages.flatMap((page) => page?.conversations?.filter(Boolean) ?? []); + }, [data?.pages]); + + const unarchiveMutation = useArchiveConvoMutation({ + onSuccess: async () => { + await refetch(); + }, + onError: () => { + showToast({ + message: localize('com_ui_unarchive_error'), + severity: NotificationSeverity.ERROR, + }); + }, + }); + + const deleteMutation = useDeleteConversationMutation({ + onSuccess: async () => { + showToast({ + message: localize('com_ui_archived_conversation_delete_success'), + severity: NotificationSeverity.SUCCESS, + }); + setIsDeleteOpen(false); + await refetch(); + }, + onError: () => { + showToast({ + message: localize('com_ui_archive_delete_error'), + severity: NotificationSeverity.ERROR, + }); + }, + }); + + const handleFetchNextPage = useCallback(async () => { + if (!hasNextPage || isFetchingNextPage) return; + + try { + await fetchNextPage(); + } catch (error) { + console.error('Failed to fetch next page:', error); + showToast({ + message: localize('com_ui_unarchive_error'), + severity: NotificationSeverity.ERROR, + }); + } + }, [fetchNextPage, hasNextPage, isFetchingNextPage, showToast, localize]); + + const confirmDelete = useCallback(() => { + if (!deleteRow?.conversationId) return; + deleteMutation.mutate({ conversationId: deleteRow.conversationId }); + }, [deleteMutation, deleteRow]); + + const { sortBy, sortDirection } = queryParams; + + const columns = useMemo( + () => [ + { + accessorKey: 'title', + header: () => ( + {localize('com_nav_archive_name')} + ), + cell: ({ row }) => { + const { conversationId, title } = row.original; + return ( + + ); + }, + meta: { + size: isSmallScreen ? '70%' : '50%', + mobileSize: '70%', + enableSorting: true, + }, + }, + { + accessorKey: 'createdAt', + header: () => ( + {localize('com_nav_archive_created_at')} + ), + cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), + meta: { + size: isSmallScreen ? '30%' : '35%', + mobileSize: '30%', + enableSorting: true, + }, + }, + { + accessorKey: 'actions', + header: () => ( + + ), + cell: ({ row }) => { + const conversation = row.original; + const isRowUnarchiving = unarchivingId === conversation.conversationId; + + return ( +
+ { + setUnarchivingId(conversation.conversationId); + unarchiveMutation.mutate( + { conversationId: conversation.conversationId, isArchived: false }, + { onSettled: () => setUnarchivingId(null) }, + ); + }} + disabled={isRowUnarchiving} + > + {isRowUnarchiving ? : } + + } + /> + { + setDeleteRow(row.original); + setIsDeleteOpen(true); + }} + > + + + } + /> +
+ ); + }, + meta: { + size: '15%', + mobileSize: '25%', + enableSorting: false, + }, + }, + ], + [isSmallScreen, localize, unarchivingId, unarchiveMutation], + ); return (
@@ -16,11 +280,49 @@ export default function ArchivedChats() { {localize('com_ui_manage')} + + + {localize('com_nav_archived_chats')} + + + + + } + showCloseButton={false} + title={localize('com_ui_delete_archived_chats')} + className="w-11/12 max-w-md" + main={ +
+
+ +
+
+ } + selection={{ + selectHandler: confirmDelete, + selectClasses: `bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white ${ + deleteMutation.isLoading ? 'cursor-not-allowed opacity-80' : '' + }`, + selectText: deleteMutation.isLoading ? : localize('com_ui_delete'), + }} />
diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx deleted file mode 100644 index ad79aed383..0000000000 --- a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx +++ /dev/null @@ -1,311 +0,0 @@ -import { useState, useCallback, useMemo, useEffect } from 'react'; -import debounce from 'lodash/debounce'; -import { useRecoilValue } from 'recoil'; -import { TrashIcon, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react'; -import { - Button, - OGDialog, - OGDialogContent, - OGDialogHeader, - OGDialogTitle, - Label, - TooltipAnchor, - Spinner, - DataTable, - useToastContext, - useMediaQuery, -} from '@librechat/client'; -import type { ConversationListParams, TConversation } from 'librechat-data-provider'; -import { - useArchiveConvoMutation, - useConversationsInfiniteQuery, - useDeleteConversationMutation, -} from '~/data-provider'; -import { MinimalIcon } from '~/components/Endpoints'; -import { NotificationSeverity } from '~/common'; -import { useLocalize } from '~/hooks'; -import { formatDate } from '~/utils'; -import store from '~/store'; - -const DEFAULT_PARAMS: ConversationListParams = { - isArchived: true, - sortBy: 'createdAt', - sortDirection: 'desc', - search: '', -}; - -export default function ArchivedChatsTable({ - onOpenChange, -}: { - onOpenChange: (isOpen: boolean) => void; -}) { - const localize = useLocalize(); - const isSmallScreen = useMediaQuery('(max-width: 768px)'); - const { showToast } = useToastContext(); - const isSearchEnabled = useRecoilValue(store.search); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [queryParams, setQueryParams] = useState(DEFAULT_PARAMS); - const [deleteConversation, setDeleteConversation] = useState(null); - - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } = - useConversationsInfiniteQuery(queryParams, { - staleTime: 0, - cacheTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnMount: false, - }); - - const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => { - setQueryParams((prev) => ({ - ...prev, - sortBy: sortField as 'title' | 'createdAt', - sortDirection: sortOrder, - })); - }, []); - - const handleFilterChange = useCallback((value: string) => { - const encodedValue = encodeURIComponent(value.trim()); - setQueryParams((prev) => ({ - ...prev, - search: encodedValue, - })); - }, []); - - const debouncedFilterChange = useMemo( - () => debounce(handleFilterChange, 300), - [handleFilterChange], - ); - - useEffect(() => { - return () => { - debouncedFilterChange.cancel(); - }; - }, [debouncedFilterChange]); - - const allConversations = useMemo(() => { - if (!data?.pages) { - return []; - } - return data.pages.flatMap((page) => page?.conversations?.filter(Boolean) ?? []); - }, [data?.pages]); - - const deleteMutation = useDeleteConversationMutation({ - onSuccess: async () => { - setIsDeleteOpen(false); - await refetch(); - }, - onError: (error: unknown) => { - showToast({ - message: localize('com_ui_archive_delete_error') as string, - severity: NotificationSeverity.ERROR, - }); - }, - }); - - const unarchiveMutation = useArchiveConvoMutation({ - onSuccess: async () => { - await refetch(); - }, - onError: (error: unknown) => { - showToast({ - message: localize('com_ui_unarchive_error') as string, - severity: NotificationSeverity.ERROR, - }); - }, - }); - - const handleFetchNextPage = useCallback(async () => { - if (!hasNextPage || isFetchingNextPage) { - return; - } - await fetchNextPage(); - }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - - const columns = useMemo( - () => [ - { - accessorKey: 'title', - header: () => { - const isSorted = queryParams.sortBy === 'title'; - const sortDirection = queryParams.sortDirection; - return ( - - ); - }, - cell: ({ row }) => { - const { conversationId, title } = row.original; - return ( - - ); - }, - meta: { - size: isSmallScreen ? '70%' : '50%', - mobileSize: '70%', - }, - }, - { - accessorKey: 'createdAt', - header: () => { - const isSorted = queryParams.sortBy === 'createdAt'; - const sortDirection = queryParams.sortDirection; - return ( - - ); - }, - cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), - meta: { - size: isSmallScreen ? '30%' : '35%', - mobileSize: '30%', - }, - }, - { - accessorKey: 'actions', - header: () => ( - - ), - cell: ({ row }) => { - const conversation = row.original; - return ( -
- - unarchiveMutation.mutate({ - conversationId: conversation.conversationId, - isArchived: false, - }) - } - title={localize('com_ui_unarchive')} - disabled={unarchiveMutation.isLoading} - > - {unarchiveMutation.isLoading ? ( - - ) : ( - - )} - - } - /> - { - setDeleteConversation(row.original); - setIsDeleteOpen(true); - }} - title={localize('com_ui_delete')} - > - - - } - /> -
- ); - }, - meta: { - size: '15%', - mobileSize: '25%', - }, - }, - ], - [handleSort, isSmallScreen, localize, queryParams, unarchiveMutation], - ); - - return ( - <> - - - - - - - {localize('com_ui_delete_confirm')} {deleteConversation?.title} - - -
- - -
-
-
- - ); -} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index c8f6bdc59a..7789820d5b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1,6 +1,6 @@ { - "chat_direction_left_to_right": "Left to Right", - "chat_direction_right_to_left": "Right to Left", + "chat_direction_left_to_right": "Chat direction set to left to right", + "chat_direction_right_to_left": "Chat direction set to right to left", "com_a11y_ai_composing": "The AI is still composing.", "com_a11y_end": "The AI has finished their reply.", "com_a11y_start": "The AI has started their reply.", @@ -390,7 +390,7 @@ "com_files_number_selected": "{{0}} of {{1}} items selected", "com_files_preparing_download": "Preparing download...", "com_files_sharepoint_picker_title": "Pick Files", - "com_files_table": "something needs to go here. was empty", + "com_files_table": "Files Table", "com_files_upload_local_machine": "From Local Computer", "com_files_upload_sharepoint": "From SharePoint", "com_generated_files": "Generated files:", @@ -748,7 +748,8 @@ "com_ui_bookmarks_title": "Title", "com_ui_bookmarks_update_error": "There was an error updating the bookmark", "com_ui_bookmarks_update_success": "Bookmark updated successfully", - "com_ui_bulk_delete_error": "Failed to delete shared links", + "com_ui_shared_link_delete_error": "Failed to delete shared link", + "com_ui_archived_chat_delete_error": "Failed to delete archived chat", "com_ui_callback_url": "Callback URL", "com_ui_cancel": "Cancel", "com_ui_cancelled": "Cancelled", @@ -832,6 +833,7 @@ "com_ui_delete_not_allowed": "Delete operation is not allowed", "com_ui_delete_prompt": "Delete Prompt?", "com_ui_delete_shared_link": "Delete shared link?", + "com_ui_delete_archived_chats": "Delete archived chat?", "com_ui_delete_success": "Successfully deleted", "com_ui_delete_tool": "Delete Tool", "com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?", @@ -850,6 +852,7 @@ "com_ui_download_backup_tooltip": "Before you continue, download your backup codes. You will need them to regain access if you lose your authenticator device", "com_ui_download_error": "Error downloading file. The file may have been deleted.", "com_ui_drag_drop": "Drop any file here to add it to the conversation", + "com_ui_drag_drop_image": "Drag and drop an image here", "com_ui_dropdown_variables": "Dropdown variables:", "com_ui_dropdown_variables_info": "Create custom dropdown menus for your prompts: `{{variable_name:option1|option2|option3}}`", "com_ui_duplicate": "Duplicate", @@ -1178,6 +1181,7 @@ "com_ui_share_var": "Share {{0}}", "com_ui_shared_link_bulk_delete_success": "Successfully deleted shared links", "com_ui_shared_link_delete_success": "Successfully deleted shared link", + "com_ui_archived_conversation_delete_success": "Successfully deleted archived conversation", "com_ui_shared_link_not_found": "Shared link not found", "com_ui_shared_prompts": "Shared Prompts", "com_ui_shop": "Shopping", diff --git a/packages/client/src/components/DataTable.tsx b/packages/client/src/components/DataTable.tsx index 37d001b4ce..56048c8af3 100644 --- a/packages/client/src/components/DataTable.tsx +++ b/packages/client/src/components/DataTable.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useRef, useState, memo, useMemo } from 'react'; +import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { Row, @@ -387,25 +388,38 @@ export default function DataTable({ {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => ( - , - isSmallScreen, - )} - onClick={ - header.column.getCanSort() - ? header.column.getToggleSortingHandler() - : undefined - } - > - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ))} + {headerGroup.headers.map((header) => { + const sortDir = header.column.getIsSorted(); + const canSort = header.column.getCanSort(); + return ( + , + isSmallScreen, + )} + > +
+ + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + {canSort && ( + + {sortDir === false && ( + + )} + {sortDir === 'asc' && } + {sortDir === 'desc' && } + + )} +
+
+ ); + })}
))}
diff --git a/packages/client/src/locales/en/translation.json b/packages/client/src/locales/en/translation.json index 9913398ff6..0c7bb41431 100644 --- a/packages/client/src/locales/en/translation.json +++ b/packages/client/src/locales/en/translation.json @@ -1,4 +1,5 @@ { "com_ui_cancel": "Cancel", - "com_ui_no_options": "No options available" + "com_ui_no_options": "No options available", + "com_ui_no_data": "No Data Available" }