fix(alarmas): add a delete action to the vacation range edit sheet
The vacation edit sheet could save changes to an existing range but had no way to remove it, forcing users back to the swipe-to-delete gesture on the list. When editing (not creating) a range, the sheet now shows an outlined delete action next to Save; it reuses the existing confirmation dialog and EstadoAlarmas.eliminarRangoVacaciones exactly as the swipe gesture already does, then pops on success.
This commit is contained in:
@@ -842,10 +842,38 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FilledButton.icon(
|
Row(
|
||||||
onPressed: _guardar,
|
children: [
|
||||||
icon: const Icon(Icons.check_rounded),
|
Expanded(
|
||||||
label: Text(l10n.saveRangeAction),
|
child: FilledButton.icon(
|
||||||
|
onPressed: _guardar,
|
||||||
|
icon: const Icon(Icons.check_rounded),
|
||||||
|
label: Text(l10n.saveRangeAction),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Fix `vacaciones-delete`: only when EDITING an existing
|
||||||
|
// range (never when creating one -- there is nothing to
|
||||||
|
// delete yet). Reuses the exact same confirmation dialog
|
||||||
|
// (`_confirmarEliminarRango`) and deletion method
|
||||||
|
// (`eliminarRangoVacaciones`) the swipe-to-delete gesture
|
||||||
|
// already uses on both `_HeroRangoActivo` and
|
||||||
|
// `_TarjetaRangoVacaciones` -- no new deletion path.
|
||||||
|
if (widget.rango != null) ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
key: const ValueKey('vacation-delete-button'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
side: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: _eliminar,
|
||||||
|
icon: const Icon(Icons.delete_outline_rounded),
|
||||||
|
label: Text(l10n.deleteAction),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -903,6 +931,21 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
|||||||
}
|
}
|
||||||
if (mounted) Navigator.pop(context);
|
if (mounted) Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fix `vacaciones-delete`: mirrors `_guardar`'s pop-on-success shape,
|
||||||
|
/// but confirms first (via the same `_confirmarEliminarRange` dialog the
|
||||||
|
/// swipe gesture uses) and calls `eliminarRangoVacaciones` instead of
|
||||||
|
/// saving. Only reachable when [widget.rango] is non-null (the delete
|
||||||
|
/// button itself is hidden otherwise).
|
||||||
|
Future<void> _eliminar() async {
|
||||||
|
final rango = widget.rango;
|
||||||
|
if (rango == null) return;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final confirmado = await _confirmarEliminarRango(context, l10n);
|
||||||
|
if (!confirmado || !mounted) return;
|
||||||
|
await context.read<EstadoAlarmas>().eliminarRangoVacaciones(rango.id);
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PickerButton extends StatelessWidget {
|
class _PickerButton extends StatelessWidget {
|
||||||
|
|||||||
@@ -614,4 +614,144 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('fix vacaciones-delete: el editor ofrece una accion de eliminar solo '
|
||||||
|
'al editar un rango existente, reusando _confirmarEliminarRango y '
|
||||||
|
'eliminarRangoVacaciones exactamente como el swipe', () {
|
||||||
|
testWidgets(
|
||||||
|
'creando un rango NUEVO (CTA "Anadir rango"), el editor NO muestra '
|
||||||
|
'una accion de eliminar -- no hay nada que borrar todavia',
|
||||||
|
(tester) async {
|
||||||
|
final estado = await _crearEstado();
|
||||||
|
addTearDown(estado.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildScreen(estado));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
final l10n = AppLocalizations.of(
|
||||||
|
tester.element(find.byType(PantallaVacaciones)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text(l10n.addVacationRangeCta));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(find.text(l10n.newVacationRangeTitle), findsOneWidget);
|
||||||
|
expect(
|
||||||
|
find.byKey(const ValueKey('vacation-delete-button')),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('editando un rango EXISTENTE (tap en su tarjeta), el editor SI '
|
||||||
|
'muestra una accion de eliminar junto al boton de guardar', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final estado = await _crearEstado(
|
||||||
|
vacaciones: [
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'f2',
|
||||||
|
nombre: 'Verano',
|
||||||
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
||||||
|
fin: _hoyDia.add(const Duration(days: 25)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
addTearDown(estado.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildScreen(estado));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
final l10n = AppLocalizations.of(
|
||||||
|
tester.element(find.byType(PantallaVacaciones)),
|
||||||
|
);
|
||||||
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
||||||
|
expect(
|
||||||
|
find.byKey(const ValueKey('vacation-delete-button')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'tocar eliminar en el editor pide confirmacion (misma que el swipe); '
|
||||||
|
'cancelar conserva el rango y el editor sigue abierto',
|
||||||
|
(tester) async {
|
||||||
|
final estado = await _crearEstado(
|
||||||
|
vacaciones: [
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'f2',
|
||||||
|
nombre: 'Verano',
|
||||||
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
||||||
|
fin: _hoyDia.add(const Duration(days: 25)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
addTearDown(estado.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildScreen(estado));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
final l10n = AppLocalizations.of(
|
||||||
|
tester.element(find.byType(PantallaVacaciones)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('vacation-delete-button')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text(l10n.cancelAction));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(estado.vacaciones, hasLength(1));
|
||||||
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('tocar eliminar en el editor y confirmar llama a '
|
||||||
|
'eliminarRangoVacaciones y cierra el editor (mismo efecto que el '
|
||||||
|
'swipe, sin pasar por _guardar)', (tester) async {
|
||||||
|
final estado = await _crearEstado(
|
||||||
|
vacaciones: [
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'f2',
|
||||||
|
nombre: 'Verano',
|
||||||
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
||||||
|
fin: _hoyDia.add(const Duration(days: 25)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
addTearDown(estado.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_buildScreen(estado));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
final l10n = AppLocalizations.of(
|
||||||
|
tester.element(find.byType(PantallaVacaciones)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('vacation-delete-button')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
||||||
|
|
||||||
|
// `find.widgetWithText(FilledButton, ...)`, not a bare
|
||||||
|
// `find.text(...)`: the sheet's own OutlinedButton delete action
|
||||||
|
// (same "Eliminar" label) is still in the tree behind the dialog,
|
||||||
|
// so a bare text finder would ambiguously match both.
|
||||||
|
await tester.tap(find.widgetWithText(FilledButton, l10n.deleteAction));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(estado.vacaciones, isEmpty);
|
||||||
|
expect(
|
||||||
|
find.text(l10n.editVacationRangeTitle),
|
||||||
|
findsNothing,
|
||||||
|
reason: 'the sheet must pop, exactly like a successful _guardar',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user