mirror of
https://github.com/documenso/documenso.git
synced 2026-07-12 05:55:12 +10:00
fix: bulk actions improvements (#2440)
This commit is contained in:
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user