add: Archive Others/All and Delete Others/All for session management
This commit is contained in:
@@ -14,6 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
|||||||
import { GridLoader } from '@/components/ui/grid-loader';
|
import { GridLoader } from '@/components/ui/grid-loader';
|
||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddLine,
|
||||||
|
RiArchiveLine,
|
||||||
RiArrowDownSLine,
|
RiArrowDownSLine,
|
||||||
RiArrowRightSLine,
|
RiArrowRightSLine,
|
||||||
RiChat4Line,
|
RiChat4Line,
|
||||||
@@ -34,6 +35,8 @@ import {
|
|||||||
RiUnpinLine,
|
RiUnpinLine,
|
||||||
} from '@remixicon/react';
|
} from '@remixicon/react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useSessionStore } from '@/stores/useSessionStore';
|
||||||
|
import { toast } from '@/components/ui';
|
||||||
import { DraggableSessionRow } from './sessionFolderDnd';
|
import { DraggableSessionRow } from './sessionFolderDnd';
|
||||||
import type { SessionNode, SessionSummaryMeta } from './types';
|
import type { SessionNode, SessionSummaryMeta } from './types';
|
||||||
import { formatSessionDateLabel, normalizePath, renderHighlightedText, resolveSessionDiffStats } from './utils';
|
import { formatSessionDateLabel, normalizePath, renderHighlightedText, resolveSessionDiffStats } from './utils';
|
||||||
@@ -159,6 +162,99 @@ export function SessionNodeItem(props: Props): React.ReactNode {
|
|||||||
const sessionSummary = session.summary as SessionSummaryMeta | undefined;
|
const sessionSummary = session.summary as SessionSummaryMeta | undefined;
|
||||||
const sessionDiffStats = resolveSessionDiffStats(sessionSummary);
|
const sessionDiffStats = resolveSessionDiffStats(sessionSummary);
|
||||||
|
|
||||||
|
const archiveSessions = useSessionStore((state) => state.archiveSessions);
|
||||||
|
const sessionsByDirectory = useSessionStore((state) => state.sessionsByDirectory);
|
||||||
|
const deleteSessions = useSessionStore((state) => state.deleteSessions);
|
||||||
|
const archivedSessions = useSessionStore((state) => state.archivedSessions);
|
||||||
|
|
||||||
|
const handleArchiveOthers = React.useCallback(async () => {
|
||||||
|
if (!sessionDirectory) return;
|
||||||
|
|
||||||
|
const sessionsInDir = sessionsByDirectory.get(sessionDirectory) ?? [];
|
||||||
|
const otherSessions = sessionsInDir.filter(s => s.id !== session.id && !s.time?.archived);
|
||||||
|
const ids = otherSessions.map(s => s.id);
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
toast.message('No other sessions to archive in this directory');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await archiveSessions(ids, { silent: true });
|
||||||
|
if (result.archivedIds.length > 0) {
|
||||||
|
toast.success(`Archived ${result.archivedIds.length} session${result.archivedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
if (result.failedIds.length > 0) {
|
||||||
|
toast.error(`Failed to archive ${result.failedIds.length} session${result.failedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
}, [sessionDirectory, session.id, sessionsByDirectory, archiveSessions]);
|
||||||
|
|
||||||
|
const handleArchiveAll = React.useCallback(async () => {
|
||||||
|
if (!sessionDirectory) return;
|
||||||
|
|
||||||
|
const sessionsInDir = sessionsByDirectory.get(sessionDirectory) ?? [];
|
||||||
|
const allSessions = sessionsInDir.filter(s => !s.time?.archived);
|
||||||
|
const ids = allSessions.map(s => s.id);
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
toast.message('No sessions to archive in this directory');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await archiveSessions(ids, { silent: true });
|
||||||
|
if (result.archivedIds.length > 0) {
|
||||||
|
toast.success(`Archived ${result.archivedIds.length} session${result.archivedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
if (result.failedIds.length > 0) {
|
||||||
|
toast.error(`Failed to archive ${result.failedIds.length} session${result.failedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
}, [sessionDirectory, sessionsByDirectory, archiveSessions]);
|
||||||
|
|
||||||
|
const handleDeleteOthers = React.useCallback(async () => {
|
||||||
|
if (!sessionDirectory) return;
|
||||||
|
|
||||||
|
const otherSessions = archivedSessions.filter(s => {
|
||||||
|
const sessionDir = normalizePath((s as Session & { directory?: string | null }).directory ?? null);
|
||||||
|
return sessionDir === sessionDirectory && s.id !== session.id;
|
||||||
|
});
|
||||||
|
const ids = otherSessions.map(s => s.id);
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
toast.message('No other sessions to delete in this directory');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await deleteSessions(ids, { silent: true });
|
||||||
|
if (result.deletedIds.length > 0) {
|
||||||
|
toast.success(`Deleted ${result.deletedIds.length} session${result.deletedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
if (result.failedIds.length > 0) {
|
||||||
|
toast.error(`Failed to delete ${result.failedIds.length} session${result.failedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
}, [sessionDirectory, session.id, archivedSessions, deleteSessions]);
|
||||||
|
|
||||||
|
const handleDeleteAll = React.useCallback(async () => {
|
||||||
|
if (!sessionDirectory) return;
|
||||||
|
|
||||||
|
const allSessions = archivedSessions.filter(s => {
|
||||||
|
const sessionDir = normalizePath((s as Session & { directory?: string | null }).directory ?? null);
|
||||||
|
return sessionDir === sessionDirectory;
|
||||||
|
});
|
||||||
|
const ids = allSessions.map(s => s.id);
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
toast.message('No sessions to delete in this directory');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await deleteSessions(ids, { silent: true });
|
||||||
|
if (result.deletedIds.length > 0) {
|
||||||
|
toast.success(`Deleted ${result.deletedIds.length} session${result.deletedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
if (result.failedIds.length > 0) {
|
||||||
|
toast.error(`Failed to delete ${result.failedIds.length} session${result.failedIds.length === 1 ? '' : 's'}`);
|
||||||
|
}
|
||||||
|
}, [sessionDirectory, archivedSessions, deleteSessions]);
|
||||||
|
|
||||||
if (editingId === session.id) {
|
if (editingId === session.id) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -481,6 +577,32 @@ export function SessionNodeItem(props: Props): React.ReactNode {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
{!archivedBucket && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuItem onClick={handleArchiveOthers}>
|
||||||
|
<RiArchiveLine className="mr-1 h-4 w-4" />
|
||||||
|
Archive Others
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={handleArchiveAll}>
|
||||||
|
<RiArchiveLine className="mr-1 h-4 w-4" />
|
||||||
|
Archive All
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{archivedBucket && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuItem onClick={handleDeleteOthers}>
|
||||||
|
<RiDeleteBinLine className="mr-1 h-4 w-4" />
|
||||||
|
Delete Others
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={handleDeleteAll}>
|
||||||
|
<RiDeleteBinLine className="mr-1 h-4 w-4" />
|
||||||
|
Delete All
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<DropdownMenuItem className="text-destructive focus:text-destructive [&>svg]:mr-1" onClick={() => handleDeleteSession(session, { archivedBucket })}>
|
<DropdownMenuItem className="text-destructive focus:text-destructive [&>svg]:mr-1" onClick={() => handleDeleteSession(session, { archivedBucket })}>
|
||||||
<RiDeleteBinLine className="mr-1 h-4 w-4" />
|
<RiDeleteBinLine className="mr-1 h-4 w-4" />
|
||||||
{archivedBucket ? 'Delete' : 'Archive'}
|
{archivedBucket ? 'Delete' : 'Archive'}
|
||||||
|
|||||||
Reference in New Issue
Block a user