From 362ac445ea28f6e36aece2a41883d5a024997405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Mon, 6 Jul 2026 11:37:38 +0200 Subject: [PATCH] fix(transactions): keep saved-filter delete button visible on touch and confirm before deleting (#648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The delete button on a saved filter was only revealed on **hover**. On desktop that's fine, but on touch devices the button is invisible yet still clickable, so users kept deleting saved filters by accident. There was also no confirmation step before a filter was deleted. ## Changes - **Always visible on touch** — the hover-reveal is now gated behind `@media (hover: hover)`. On hover-capable devices the trash icon still appears only on hover; on touch devices (`hover: none`) it stays visible. - **Confirm before deleting** — deleting a saved filter now opens an `AlertDialog` confirmation ("Are you sure you want to delete "…"?"), matching the existing delete-confirmation pattern used across the app. Cancel/Escape dismiss without deleting. ## QA Tested end-to-end in a real browser (mobile emulation, `hover: none`): - Desktop (`hover: hover`): delete button computes `opacity: 0` until hover ✓ - Touch (`hover: none`): delete button computes `opacity: 1`, visible without hover ✓ - Tapping the trash icon opens the confirmation dialog with the correct filter name ✓ - **Cancel** keeps the filter (verified in DB) ✓ - **Delete** removes it (verified in DB) ✓ ## Demo qa-01-desktop-dropdown qa-02-confirm-dialog qa-03-after-delete qa-04-mobile-dropdown --- lang/es.json | 1 + .../components/transactions/saved-filters.tsx | 62 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lang/es.json b/lang/es.json index c589d735..9d71af0c 100644 --- a/lang/es.json +++ b/lang/es.json @@ -118,6 +118,7 @@ "Your bank account is connected.": "Tu cuenta bancaria está conectada.", "A filter with that name already exists": "Ya existe un filtro con ese nombre", "Delete saved filter": "Eliminar filtro guardado", + "Are you sure you want to delete “:name”? This action cannot be undone.": "¿Seguro que quieres eliminar «:name»? Esta acción no se puede deshacer.", "Failed to delete the saved filter": "No se pudo eliminar el filtro guardado", "Failed to save the filter": "No se pudo guardar el filtro", "Failed to update the saved filter": "No se pudo actualizar el filtro guardado", diff --git a/resources/js/components/transactions/saved-filters.tsx b/resources/js/components/transactions/saved-filters.tsx index 7514e9f7..e73833ba 100644 --- a/resources/js/components/transactions/saved-filters.tsx +++ b/resources/js/components/transactions/saved-filters.tsx @@ -1,3 +1,13 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; import { Button } from '@/components/ui/button'; import { Dialog, @@ -36,7 +46,7 @@ import { Save, Trash2, } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { toast } from 'sonner'; interface SavedFilter { @@ -54,6 +64,15 @@ export function SavedFilters({ filters, onLoad }: SavedFiltersProps) { const [savedFilters, setSavedFilters] = useState([]); const [activeId, setActiveId] = useState(null); const [saveDialogOpen, setSaveDialogOpen] = useState(false); + const [pendingDelete, setPendingDelete] = useState( + null, + ); + // Retain the last name so it stays visible during the close animation, + // once pendingDelete has already been cleared to null. + const pendingDeleteName = useRef(''); + if (pendingDelete) { + pendingDeleteName.current = pendingDelete.name; + } const [name, setName] = useState(''); const [isSaving, setIsSaving] = useState(false); @@ -253,11 +272,11 @@ export function SavedFilters({ filters, onLoad }: SavedFiltersProps) {