From 3535c25c8386d967537e567559ee7597f590e530 Mon Sep 17 00:00:00 2001 From: asemyanov Date: Thu, 21 Aug 2025 20:58:29 +0200 Subject: [PATCH] tasks time grouping --- .../src/components/sidebar/nav-agents.tsx | 276 +++++++++++------- .../hooks/react-query/sidebar/use-sidebar.ts | 41 +++ 2 files changed, 211 insertions(+), 106 deletions(-) diff --git a/frontend/src/components/sidebar/nav-agents.tsx b/frontend/src/components/sidebar/nav-agents.tsx index 492f7738..ef06538f 100644 --- a/frontend/src/components/sidebar/nav-agents.tsx +++ b/frontend/src/components/sidebar/nav-agents.tsx @@ -44,10 +44,147 @@ import { DeleteConfirmationDialog } from "@/components/thread/DeleteConfirmation import { useDeleteOperation } from '@/contexts/DeleteOperationContext' import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" -import { ThreadWithProject } from '@/hooks/react-query/sidebar/use-sidebar'; -import { processThreadsWithProjects, useDeleteMultipleThreads, useDeleteThread, useProjects, useThreads } from '@/hooks/react-query/sidebar/use-sidebar'; +import { ThreadWithProject, GroupedThreads } from '@/hooks/react-query/sidebar/use-sidebar'; +import { processThreadsWithProjects, useDeleteMultipleThreads, useDeleteThread, useProjects, useThreads, groupThreadsByDate } from '@/hooks/react-query/sidebar/use-sidebar'; import { projectKeys, threadKeys } from '@/hooks/react-query/sidebar/keys'; +// Component for date group headers +const DateGroupHeader: React.FC<{ dateGroup: string; count: number }> = ({ dateGroup, count }) => { + return ( +
+
+ {dateGroup} ({count}) +
+
+ ); +}; + +// Component for individual thread item +const ThreadItem: React.FC<{ + thread: ThreadWithProject; + isActive: boolean; + isThreadLoading: boolean; + isSelected: boolean; + selectedThreads: Set; + loadingThreadId: string | null; + pathname: string | null; + isMobile: boolean; + handleThreadClick: (e: React.MouseEvent, threadId: string, url: string) => void; + toggleThreadSelection: (threadId: string, e?: React.MouseEvent) => void; + handleDeleteThread: (threadId: string, threadName: string) => void; + setSelectedItem: (item: { threadId: string; projectId: string } | null) => void; + setShowShareModal: (show: boolean) => void; +}> = ({ + thread, + isActive, + isThreadLoading, + isSelected, + handleThreadClick, + toggleThreadSelection, + handleDeleteThread, + setSelectedItem, + setShowShareModal, + isMobile +}) => { + return ( + + +
+ + handleThreadClick(e, thread.threadId, thread.url) + } + prefetch={false} + className="flex items-center flex-1 min-w-0 touch-manipulation" + > + {isThreadLoading ? ( + + ) : null} + {thread.projectName} + + + {/* Checkbox - only visible on hover of this specific area */} +
toggleThreadSelection(thread.threadId, e)} + > +
+ {isSelected && } +
+
+ + {/* Dropdown Menu - inline with content */} + + + + + + { + setSelectedItem({ threadId: thread?.threadId, projectId: thread?.projectId }) + setShowShareModal(true) + }}> + + Share Chat + + + + + Open in New Tab + + + + + handleDeleteThread( + thread.threadId, + thread.projectName, + ) + } + > + + Delete + + + +
+
+
+ ); +}; + export function NavAgents() { const { isMobile, state, setOpenMobile } = useSidebar() const [loadingThreadId, setLoadingThreadId] = useState(null) @@ -88,6 +225,8 @@ export function NavAgents() { !isProjectsLoading && !isThreadsLoading ? processThreadsWithProjects(threads, projects) : []; + const groupedThreads: GroupedThreads = groupThreadsByDate(combinedThreads); + const handleDeletionProgress = (completed: number, total: number) => { const percentage = (completed / total) * 100; setDeleteProgress(percentage); @@ -401,112 +540,37 @@ export function NavAgents() { )) ) : combinedThreads.length > 0 ? ( - // Show all threads with project info + // Show threads grouped by date <> - {combinedThreads.map((thread) => { - // Check if this thread is currently active - const isActive = pathname?.includes(thread.threadId) || false; - const isThreadLoading = loadingThreadId === thread.threadId; - const isSelected = selectedThreads.has(thread.threadId); + {Object.entries(groupedThreads).map(([dateGroup, threadsInGroup]) => ( +
+ + {threadsInGroup.map((thread) => { + const isActive = pathname?.includes(thread.threadId) || false; + const isThreadLoading = loadingThreadId === thread.threadId; + const isSelected = selectedThreads.has(thread.threadId); - return ( - - -
- - handleThreadClick(e, thread.threadId, thread.url) - } - prefetch={false} - className="flex items-center flex-1 min-w-0 touch-manipulation" - > - {isThreadLoading ? ( - - ) : null} - {thread.projectName} - - - {/* Checkbox - only visible on hover of this specific area */} -
toggleThreadSelection(thread.threadId, e)} - > -
- {isSelected && } -
-
- - {/* Dropdown Menu - inline with content */} - - - - - - { - setSelectedItem({ threadId: thread?.threadId, projectId: thread?.projectId }) - setShowShareModal(true) - }}> - - Share Chat - - - - - Open in New Tab - - - - - handleDeleteThread( - thread.threadId, - thread.projectName, - ) - } - > - - Delete - - - -
-
-
- ); - })} + return ( + + ); + })} +
+ ))} ) : ( diff --git a/frontend/src/hooks/react-query/sidebar/use-sidebar.ts b/frontend/src/hooks/react-query/sidebar/use-sidebar.ts index 784a1425..13367f40 100644 --- a/frontend/src/hooks/react-query/sidebar/use-sidebar.ts +++ b/frontend/src/hooks/react-query/sidebar/use-sidebar.ts @@ -132,4 +132,45 @@ export const sortThreads = ( return [...threadsList].sort((a, b) => { return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); }); +}; + +export type GroupedThreads = { + [dateGroup: string]: ThreadWithProject[]; +}; + +export const groupThreadsByDate = ( + threadsList: ThreadWithProject[] +): GroupedThreads => { + const sortedThreads = sortThreads(threadsList); + const grouped: GroupedThreads = {}; + const now = new Date(); + + sortedThreads.forEach(thread => { + const threadDate = new Date(thread.updatedAt); + const diffInMs = now.getTime() - threadDate.getTime(); + const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24)); + + let dateGroup: string; + + if (diffInDays === 0) { + dateGroup = 'Today'; + } else if (diffInDays === 1) { + dateGroup = 'Yesterday'; + } else if (diffInDays <= 7) { + dateGroup = 'This Week'; + } else if (diffInDays <= 30) { + dateGroup = 'This Month'; + } else if (diffInDays <= 90) { + dateGroup = 'Last 3 Months'; + } else { + dateGroup = 'Older'; + } + + if (!grouped[dateGroup]) { + grouped[dateGroup] = []; + } + grouped[dateGroup].push(thread); + }); + + return grouped; }; \ No newline at end of file