fix: bulk actions improvements (#2440)

This commit is contained in:
Catalin Pit
2026-02-10 11:13:03 +02:00
committed by GitHub
parent a5ef1d23e6
commit f1c91c4951
5 changed files with 88 additions and 22 deletions
@@ -149,7 +149,12 @@ export const EnvelopesBulkDeleteDialog = ({
</Alert>
<DialogFooter>
<Button type="button" variant="secondary" disabled={isPending}>
<Button
type="button"
variant="secondary"
onClick={() => onOpenChange(false)}
disabled={isPending}
>
<Trans>Cancel</Trans>
</Button>
@@ -24,7 +24,7 @@ export const EnvelopesTableBulkActionBar = ({
}
return (
<div className="fixed bottom-4 left-1/2 z-50 flex -translate-x-1/2 items-center gap-x-4 rounded-lg border border-border bg-widget px-4 py-3 shadow-lg">
<div className="fixed bottom-4 left-1/2 z-50 flex -translate-x-1/2 items-center gap-x-4 rounded-lg border border-border bg-background px-4 py-3 shadow-lg">
<span className="text-sm font-medium">
<Trans>{selectedCount} selected</Trans>
</span>
@@ -36,13 +36,7 @@ export const EnvelopesTableBulkActionBar = ({
<Trans>Move to Folder</Trans>
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={onDeleteClick}
className="text-destructive hover:text-destructive"
>
<Button type="button" variant="destructive" size="sm" onClick={onDeleteClick}>
<Trash2Icon className="mr-2 h-4 w-4" />
<Trans>Delete</Trans>
</Button>
@@ -7,6 +7,7 @@ import { useParams, useSearchParams } from 'react-router';
import { Link } from 'react-router';
import { z } from 'zod';
import { useSessionStorage } from '@documenso/lib/client-only/hooks/use-session-storage';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
import { parseToIntegerArray } from '@documenso/lib/utils/params';
@@ -58,7 +59,10 @@ export default function DocumentsPage() {
const [isMovingDocument, setIsMovingDocument] = useState(false);
const [documentToMove, setDocumentToMove] = useState<number | null>(null);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [rowSelection, setRowSelection] = useSessionStorage<RowSelectionState>(
'documents-bulk-selection',
{},
);
const [isBulkMoveDialogOpen, setIsBulkMoveDialogOpen] = useState(false);
const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false);
@@ -121,11 +125,6 @@ export default function DocumentsPage() {
}
}, [data?.stats]);
// Clear selection when navigation or filters change
useEffect(() => {
setRowSelection({});
}, [folderId, findDocumentSearchParams]);
return (
<EnvelopeDropZoneWrapper type={EnvelopeType.DOCUMENT}>
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
@@ -1,10 +1,11 @@
import { useEffect, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import { Trans } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import { Bird } from 'lucide-react';
import { useParams, useSearchParams } from 'react-router';
import { useSessionStorage } from '@documenso/lib/client-only/hooks/use-session-storage';
import { FolderType } from '@documenso/lib/types/folder-type';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
@@ -34,7 +35,10 @@ export default function TemplatesPage() {
const page = Number(searchParams.get('page')) || 1;
const perPage = Number(searchParams.get('perPage')) || 10;
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [rowSelection, setRowSelection] = useSessionStorage<RowSelectionState>(
'templates-bulk-selection',
{},
);
const [isBulkMoveDialogOpen, setIsBulkMoveDialogOpen] = useState(false);
const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false);
@@ -51,11 +55,6 @@ export default function TemplatesPage() {
folderId,
});
// Clear selection when navigation or filters change
useEffect(() => {
setRowSelection({});
}, [folderId, page, perPage]);
return (
<EnvelopeDropZoneWrapper type={EnvelopeType.TEMPLATE}>
<div className="mx-auto max-w-screen-xl px-4 md:px-8">
@@ -0,0 +1,69 @@
import * as React from 'react';
import type { Dispatch, SetStateAction } from 'react';
function dispatchStorageEvent(key: string, newValue: string | null) {
window.dispatchEvent(new StorageEvent('storage', { key, newValue }));
}
const setSessionStorageItem = <T>(key: string, value: T) => {
const stringifiedValue = JSON.stringify(value);
window.sessionStorage.setItem(key, stringifiedValue);
dispatchStorageEvent(key, stringifiedValue);
};
const removeSessionStorageItem = (key: string) => {
window.sessionStorage.removeItem(key);
dispatchStorageEvent(key, null);
};
const getSessionStorageItem = (key: string) => {
return window.sessionStorage.getItem(key);
};
const useSessionStorageSubscribe = (callback: (event: StorageEvent) => void) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
export function useSessionStorage<T>(
key: string,
initialValue: T,
): [T, Dispatch<SetStateAction<T>>] {
const serializedInitialValue = JSON.stringify(initialValue);
const getSnapshot = () => getSessionStorageItem(key);
const getServerSnapshot = () => serializedInitialValue;
const store = React.useSyncExternalStore(
useSessionStorageSubscribe,
getSnapshot,
getServerSnapshot,
);
const setState: Dispatch<SetStateAction<T>> = React.useCallback(
(v) => {
try {
const prevValue = store ? JSON.parse(store) : initialValue;
// @ts-expect-error - SetStateAction function check is safe at runtime
const nextState = typeof v === 'function' ? v(prevValue) : v;
if (nextState === undefined || nextState === null) {
removeSessionStorageItem(key);
} else {
setSessionStorageItem(key, nextState);
}
} catch (e) {
console.warn(e);
}
},
[key, store, initialValue],
);
React.useEffect(() => {
if (getSessionStorageItem(key) === null && typeof initialValue !== 'undefined') {
setSessionStorageItem(key, initialValue);
}
}, [key, initialValue]);
return [store ? JSON.parse(store) : initialValue, setState];
}