feat: loading toasts for all photo actions

Add loading→success/error toast transition to every bulk operation
(archive, restore, delete, approve, add-to-heap, metadata patch).
Also wires gridKeyNav + CauseGroupCard into the bulkAction store so
keyboard-triggered actions show the same per-tile pending/done/error
feedback as BulkActionBar buttons.
This commit is contained in:
2026-06-07 21:40:18 +02:00
parent da63ad769a
commit 5da1022ed1
7 changed files with 88 additions and 80 deletions

View File

@@ -66,21 +66,20 @@ export function cachedPhoto(uid: string): PpPhoto | undefined {
*/
export async function dismissPhotos(uids: string[]): Promise<void> {
if (uids.length === 0) return;
const tid = toast.loading(`Dismissing ${uids.length}`);
const { updated, errors } = await batchEdit(uids, (id) => approvePhoto(id));
// Advance focus past the dismissed set before the timeline refetches
// so the cursor doesn't snap back to photo[0]; clear the now-stale
// selection ring for the same reason.
focusAfter(uids);
clearSelection();
invalidatePhotos(uids);
void queryClient.invalidateQueries({ queryKey: ['review-groups'] });
if (errors.length) {
toast.error(`Dismissed ${updated.length}; ${errors.length} failed`, {
id: tid,
description: errors[0].message
});
return;
}
toast.success(`Dismissed ${uids.length}`);
toast.success(`Dismissed ${uids.length}`, { id: tid });
}
/**
@@ -93,6 +92,7 @@ export async function dismissPhotos(uids: string[]): Promise<void> {
*/
export async function acceptDateAndKeep(uids: string[]): Promise<void> {
if (uids.length === 0) return;
const tid = toast.loading(`Updating & keeping ${uids.length}`);
const { updated, errors } = await batchEdit(uids, async (id) => {
const p = cachedPhoto(id);
if (p) {
@@ -113,11 +113,12 @@ export async function acceptDateAndKeep(uids: string[]): Promise<void> {
void queryClient.invalidateQueries({ queryKey: ['review-groups'] });
if (errors.length) {
toast.error(`Kept ${updated.length}; ${errors.length} failed`, {
id: tid,
description: errors[0].message
});
return;
}
toast.success(`Kept ${uids.length}`);
toast.success(`Kept ${uids.length}`, { id: tid });
}
/**
@@ -125,10 +126,11 @@ export async function acceptDateAndKeep(uids: string[]): Promise<void> {
*/
export async function archivePhotos(uids: string[]): Promise<void> {
if (uids.length === 0) return;
const tid = toast.loading(`Archiving ${uids.length}`);
try {
await batchArchive(uids);
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Archive failed');
toast.error(err instanceof Error ? err.message : 'Archive failed', { id: tid });
return;
}
pushUndo(`Archived ${uids.length}`, async () => {
@@ -140,5 +142,5 @@ export async function archivePhotos(uids: string[]): Promise<void> {
clearSelection();
invalidatePhotos(uids);
void queryClient.invalidateQueries({ queryKey: ['review-groups'] });
toast.success(`Archived ${uids.length}`);
toast.success(`Archived ${uids.length}`, { id: tid });
}