fix(vacaciones): edit and delete vacation ranges
Vacaciones ranges could be created but never edited or removed -- EstadoAlarmas already had crearRangoVacaciones/eliminarRangoVacaciones with no UI affordance reaching them, and no update path at all. Add EstadoAlarmas.editarRangoVacaciones and wire tap-to-edit / swipe-to-delete (with confirmation) onto every range card, mirroring the alarm list's own Dismissible + confirm-dialog pattern exactly. This covers the active-range hero too: a freshly created range is active immediately and only ever renders there, never in the scheduled/past lists, so it needed the same affordances or a user's very first range could never be fixed.
This commit is contained in:
@@ -354,6 +354,19 @@ class EstadoAlarmas extends ChangeNotifier {
|
||||
await guardarVacaciones(nuevos);
|
||||
}
|
||||
|
||||
/// Issue 1 (feedback-pruebas): replaces the range with the same [id] in
|
||||
/// place -- the counterpart `crearRangoVacaciones`/`eliminarRangoVacaciones`
|
||||
/// were missing before this fix, leaving no way to fix a mistake in an
|
||||
/// already-saved range (including the currently ACTIVE one, since a
|
||||
/// freshly created range starts active immediately).
|
||||
Future<void> editarRangoVacaciones(RangoVacaciones rango) async {
|
||||
final nuevos = [
|
||||
for (final actual in _vacaciones)
|
||||
if (actual.id == rango.id) rango else actual,
|
||||
];
|
||||
await guardarVacaciones(nuevos);
|
||||
}
|
||||
|
||||
// ── Vacation queries (design ADR-6, WU9) ──────────────────────────────
|
||||
// Four PURE queries: none writes, none reschedules, none touches the
|
||||
// native bridge. Read-only over `_alarmas`/`_vacaciones`. Every method
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "حذف النطاق",
|
||||
"vacationsDefaultName": "إجازات",
|
||||
"newVacationRangeTitle": "نطاق إجازة جديد",
|
||||
"editVacationRangeTitle": "تعديل نطاق الإجازة",
|
||||
"vacationDeleteConfirmTitle": "هل تريد حذف نطاق الإجازة؟",
|
||||
"vacationDeleteConfirmMessage": "لا يمكن التراجع عن هذا الإجراء.",
|
||||
"startField": "البداية",
|
||||
"endField": "النهاية",
|
||||
"saveRangeAction": "حفظ النطاق",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "পরিসর মুছুন",
|
||||
"vacationsDefaultName": "ছুটি",
|
||||
"newVacationRangeTitle": "নতুন ছুটির পরিসর",
|
||||
"editVacationRangeTitle": "ছুটির পরিসর সম্পাদনা করুন",
|
||||
"vacationDeleteConfirmTitle": "ছুটির পরিসর মুছবেন?",
|
||||
"vacationDeleteConfirmMessage": "এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।",
|
||||
"startField": "শুরু",
|
||||
"endField": "শেষ",
|
||||
"saveRangeAction": "পরিসর সংরক্ষণ করুন",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Zeitraum löschen",
|
||||
"vacationsDefaultName": "Ferien",
|
||||
"newVacationRangeTitle": "Neuer Ferienzeitraum",
|
||||
"editVacationRangeTitle": "Ferienzeitraum bearbeiten",
|
||||
"vacationDeleteConfirmTitle": "Ferienzeitraum löschen?",
|
||||
"vacationDeleteConfirmMessage": "Dies kann nicht rückgängig gemacht werden.",
|
||||
"startField": "Beginn",
|
||||
"endField": "Ende",
|
||||
"saveRangeAction": "Zeitraum speichern",
|
||||
|
||||
@@ -580,6 +580,9 @@
|
||||
"vacationNoActiveRangeHint": "No active vacation range right now.",
|
||||
"vacationsDefaultName": "Vacation",
|
||||
"newVacationRangeTitle": "New vacation range",
|
||||
"editVacationRangeTitle": "Edit vacation range",
|
||||
"vacationDeleteConfirmTitle": "Delete vacation range?",
|
||||
"vacationDeleteConfirmMessage": "This can't be undone.",
|
||||
"startField": "Start",
|
||||
"endField": "End",
|
||||
"saveRangeAction": "Save range",
|
||||
|
||||
@@ -580,6 +580,9 @@
|
||||
"vacationNoActiveRangeHint": "No hay un rango de vacaciones activo ahora mismo.",
|
||||
"vacationsDefaultName": "Vacaciones",
|
||||
"newVacationRangeTitle": "Nuevo rango de vacaciones",
|
||||
"editVacationRangeTitle": "Editar rango de vacaciones",
|
||||
"vacationDeleteConfirmTitle": "¿Eliminar rango de vacaciones?",
|
||||
"vacationDeleteConfirmMessage": "Esta acción no se puede deshacer.",
|
||||
"startField": "Inicio",
|
||||
"endField": "Fin",
|
||||
"saveRangeAction": "Guardar rango",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Supprimer la période",
|
||||
"vacationsDefaultName": "Vacances",
|
||||
"newVacationRangeTitle": "Nouvelle période de vacances",
|
||||
"editVacationRangeTitle": "Modifier la période de vacances",
|
||||
"vacationDeleteConfirmTitle": "Supprimer la période de vacances ?",
|
||||
"vacationDeleteConfirmMessage": "Cette action est irréversible.",
|
||||
"startField": "Début",
|
||||
"endField": "Fin",
|
||||
"saveRangeAction": "Enregistrer la période",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "अवधि हटाएँ",
|
||||
"vacationsDefaultName": "छुट्टियाँ",
|
||||
"newVacationRangeTitle": "नई छुट्टी अवधि",
|
||||
"editVacationRangeTitle": "छुट्टी अवधि संपादित करें",
|
||||
"vacationDeleteConfirmTitle": "छुट्टी अवधि हटाएं?",
|
||||
"vacationDeleteConfirmMessage": "इसे वापस नहीं लिया जा सकता।",
|
||||
"startField": "शुरुआत",
|
||||
"endField": "समाप्ति",
|
||||
"saveRangeAction": "अवधि सहेजें",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Hapus rentang",
|
||||
"vacationsDefaultName": "Liburan",
|
||||
"newVacationRangeTitle": "Rentang liburan baru",
|
||||
"editVacationRangeTitle": "Edit rentang liburan",
|
||||
"vacationDeleteConfirmTitle": "Hapus rentang liburan?",
|
||||
"vacationDeleteConfirmMessage": "Tindakan ini tidak dapat dibatalkan.",
|
||||
"startField": "Mulai",
|
||||
"endField": "Akhir",
|
||||
"saveRangeAction": "Simpan rentang",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Elimina periodo",
|
||||
"vacationsDefaultName": "Vacanze",
|
||||
"newVacationRangeTitle": "Nuovo periodo di vacanza",
|
||||
"editVacationRangeTitle": "Modifica periodo di vacanza",
|
||||
"vacationDeleteConfirmTitle": "Eliminare il periodo di vacanza?",
|
||||
"vacationDeleteConfirmMessage": "Questa azione non può essere annullata.",
|
||||
"startField": "Inizio",
|
||||
"endField": "Fine",
|
||||
"saveRangeAction": "Salva periodo",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "期間を削除",
|
||||
"vacationsDefaultName": "休暇",
|
||||
"newVacationRangeTitle": "新しい休暇期間",
|
||||
"editVacationRangeTitle": "休暇期間を編集",
|
||||
"vacationDeleteConfirmTitle": "休暇期間を削除しますか?",
|
||||
"vacationDeleteConfirmMessage": "この操作は元に戻せません。",
|
||||
"startField": "開始",
|
||||
"endField": "終了",
|
||||
"saveRangeAction": "期間を保存",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Excluir período",
|
||||
"vacationsDefaultName": "Férias",
|
||||
"newVacationRangeTitle": "Novo período de férias",
|
||||
"editVacationRangeTitle": "Editar período de férias",
|
||||
"vacationDeleteConfirmTitle": "Excluir período de férias?",
|
||||
"vacationDeleteConfirmMessage": "Esta ação não pode ser desfeita.",
|
||||
"startField": "Início",
|
||||
"endField": "Fim",
|
||||
"saveRangeAction": "Salvar período",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "Удалить период",
|
||||
"vacationsDefaultName": "Отпуск",
|
||||
"newVacationRangeTitle": "Новый период отпуска",
|
||||
"editVacationRangeTitle": "Изменить период отпуска",
|
||||
"vacationDeleteConfirmTitle": "Удалить период отпуска?",
|
||||
"vacationDeleteConfirmMessage": "Это действие нельзя отменить.",
|
||||
"startField": "Начало",
|
||||
"endField": "Конец",
|
||||
"saveRangeAction": "Сохранить период",
|
||||
|
||||
@@ -468,6 +468,9 @@
|
||||
"deleteRangeTooltip": "删除范围",
|
||||
"vacationsDefaultName": "假期",
|
||||
"newVacationRangeTitle": "新的假期范围",
|
||||
"editVacationRangeTitle": "编辑假期范围",
|
||||
"vacationDeleteConfirmTitle": "删除假期范围?",
|
||||
"vacationDeleteConfirmMessage": "此操作无法撤销。",
|
||||
"startField": "开始",
|
||||
"endField": "结束",
|
||||
"saveRangeAction": "保存范围",
|
||||
|
||||
@@ -2078,6 +2078,24 @@ abstract class AppLocalizations {
|
||||
/// **'Nuevo rango de vacaciones'**
|
||||
String get newVacationRangeTitle;
|
||||
|
||||
/// No description provided for @editVacationRangeTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Editar rango de vacaciones'**
|
||||
String get editVacationRangeTitle;
|
||||
|
||||
/// No description provided for @vacationDeleteConfirmTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'¿Eliminar rango de vacaciones?'**
|
||||
String get vacationDeleteConfirmTitle;
|
||||
|
||||
/// No description provided for @vacationDeleteConfirmMessage.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Esta acción no se puede deshacer.'**
|
||||
String get vacationDeleteConfirmMessage;
|
||||
|
||||
/// No description provided for @startField.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
|
||||
@@ -1133,6 +1133,15 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'نطاق إجازة جديد';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'تعديل نطاق الإجازة';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'هل تريد حذف نطاق الإجازة؟';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'لا يمكن التراجع عن هذا الإجراء.';
|
||||
|
||||
@override
|
||||
String get startField => 'البداية';
|
||||
|
||||
|
||||
@@ -1140,6 +1140,16 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'নতুন ছুটির পরিসর';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'ছুটির পরিসর সম্পাদনা করুন';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'ছুটির পরিসর মুছবেন?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage =>
|
||||
'এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।';
|
||||
|
||||
@override
|
||||
String get startField => 'শুরু';
|
||||
|
||||
|
||||
@@ -1142,6 +1142,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Neuer Ferienzeitraum';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Ferienzeitraum bearbeiten';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Ferienzeitraum löschen?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage =>
|
||||
'Dies kann nicht rückgängig gemacht werden.';
|
||||
|
||||
@override
|
||||
String get startField => 'Beginn';
|
||||
|
||||
|
||||
@@ -1133,6 +1133,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'New vacation range';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Edit vacation range';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Delete vacation range?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'This can\'t be undone.';
|
||||
|
||||
@override
|
||||
String get startField => 'Start';
|
||||
|
||||
|
||||
@@ -1139,6 +1139,16 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Nuevo rango de vacaciones';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Editar rango de vacaciones';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => '¿Eliminar rango de vacaciones?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage =>
|
||||
'Esta acción no se puede deshacer.';
|
||||
|
||||
@override
|
||||
String get startField => 'Inicio';
|
||||
|
||||
|
||||
@@ -1145,6 +1145,15 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Nouvelle période de vacances';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Modifier la période de vacances';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Supprimer la période de vacances ?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'Cette action est irréversible.';
|
||||
|
||||
@override
|
||||
String get startField => 'Début';
|
||||
|
||||
|
||||
@@ -1134,6 +1134,15 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'नई छुट्टी अवधि';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'छुट्टी अवधि संपादित करें';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'छुट्टी अवधि हटाएं?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'इसे वापस नहीं लिया जा सकता।';
|
||||
|
||||
@override
|
||||
String get startField => 'शुरुआत';
|
||||
|
||||
|
||||
@@ -1139,6 +1139,16 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Rentang liburan baru';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Edit rentang liburan';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Hapus rentang liburan?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage =>
|
||||
'Tindakan ini tidak dapat dibatalkan.';
|
||||
|
||||
@override
|
||||
String get startField => 'Mulai';
|
||||
|
||||
|
||||
@@ -1144,6 +1144,16 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Nuovo periodo di vacanza';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Modifica periodo di vacanza';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Eliminare il periodo di vacanza?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage =>
|
||||
'Questa azione non può essere annullata.';
|
||||
|
||||
@override
|
||||
String get startField => 'Inizio';
|
||||
|
||||
|
||||
@@ -1101,6 +1101,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => '新しい休暇期間';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => '休暇期間を編集';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => '休暇期間を削除しますか?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'この操作は元に戻せません。';
|
||||
|
||||
@override
|
||||
String get startField => '開始';
|
||||
|
||||
|
||||
@@ -1138,6 +1138,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Novo período de férias';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Editar período de férias';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Excluir período de férias?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'Esta ação não pode ser desfeita.';
|
||||
|
||||
@override
|
||||
String get startField => 'Início';
|
||||
|
||||
|
||||
@@ -1140,6 +1140,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => 'Новый период отпуска';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => 'Изменить период отпуска';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => 'Удалить период отпуска?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => 'Это действие нельзя отменить.';
|
||||
|
||||
@override
|
||||
String get startField => 'Начало';
|
||||
|
||||
|
||||
@@ -1097,6 +1097,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get newVacationRangeTitle => '新的假期范围';
|
||||
|
||||
@override
|
||||
String get editVacationRangeTitle => '编辑假期范围';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmTitle => '删除假期范围?';
|
||||
|
||||
@override
|
||||
String get vacationDeleteConfirmMessage => '此操作无法撤销。';
|
||||
|
||||
@override
|
||||
String get startField => '开始';
|
||||
|
||||
|
||||
@@ -82,13 +82,78 @@ class PantallaVacaciones extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _abrirAlta(BuildContext context) async {
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => const _EditorVacacionesSheet(),
|
||||
Future<void> _abrirAlta(BuildContext context) =>
|
||||
_abrirEditorVacaciones(context);
|
||||
}
|
||||
|
||||
/// Issue 1 (feedback-pruebas): the ONE sheet-opener both the header/CTA
|
||||
/// "create" entry points and every range's own "tap to edit" affordance call
|
||||
/// -- passing [rango] switches the sheet from create to edit mode (mirrors
|
||||
/// `pantalla_alarmas.dart`'s `_abrirEditor`/`_EditorAlarmaSheet` split).
|
||||
Future<void> _abrirEditorVacaciones(
|
||||
BuildContext context, {
|
||||
RangoVacaciones? rango,
|
||||
}) async {
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => _EditorVacacionesSheet(rango: rango),
|
||||
);
|
||||
}
|
||||
|
||||
/// Issue 1 (feedback-pruebas): mirrors `pantalla_alarmas.dart`'s
|
||||
/// `_confirmarEliminarAlarma` exactly -- same AlertDialog shape, same
|
||||
/// generic delete/cancel actions, only the copy is vacation-specific.
|
||||
Future<bool> _confirmarEliminarRango(
|
||||
BuildContext context,
|
||||
AppLocalizations l10n,
|
||||
) async {
|
||||
final confirmado = await showDialog<bool>(
|
||||
context: context,
|
||||
builder:
|
||||
(ctx) => AlertDialog(
|
||||
title: Text(l10n.vacationDeleteConfirmTitle),
|
||||
content: Text(l10n.vacationDeleteConfirmMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: Text(l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: Text(l10n.deleteAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return confirmado ?? false;
|
||||
}
|
||||
|
||||
/// Swipe-to-delete reveal shown on both sides, mirroring
|
||||
/// `pantalla_alarmas.dart`'s `_FondoSwipeEliminarAlarma` -- duplicated
|
||||
/// rather than shared, matching this codebase's own precedent for tiny
|
||||
/// per-screen chrome (see this file's `_DashedBorderPainter` doc comment).
|
||||
class _FondoSwipeEliminarRango extends StatelessWidget {
|
||||
const _FondoSwipeEliminarRango({required this.alignment});
|
||||
|
||||
final Alignment alignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.pluriTokens;
|
||||
return Container(
|
||||
alignment: alignment,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
borderRadius: BorderRadius.circular(tokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -242,46 +307,81 @@ class _HeroRangoActivo extends StatelessWidget {
|
||||
final diasRestantes = rango.finDia.difference(hoyDia).inDays;
|
||||
final impacto = estado.impactoDeRango(rango);
|
||||
final type = context.pluriType;
|
||||
final tokens = context.pluriTokens;
|
||||
|
||||
return PluriGlassSurface(
|
||||
glowColor: context.pluriTokens.electricMagenta.withValues(alpha: 0.24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
localizedVacationName(l10n, rango.nombre),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Item 21 / audit 9b.4 (t4:454): the "active now" caption is a
|
||||
// teal eyebrow, not default body text.
|
||||
Text(
|
||||
l10n.vacationSummaryActiveCountdown(diasRestantes),
|
||||
style: type.eyebrowLabel.copyWith(color: PluriWaveTokens.brand),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Item 21 / audit 9b.4 (t4:451-462): the screen's signature
|
||||
// element is a start/end date pair joined by a gradient rule —
|
||||
// the prototype never draws a determinate progress bar here.
|
||||
_ParFechasVacaciones(
|
||||
inicio: rango.inicioDia,
|
||||
fin: rango.finDia,
|
||||
destacado: true,
|
||||
reglaKey: const ValueKey('vacaciones-regla-activo'),
|
||||
),
|
||||
if (impacto.pausadas.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(l10n.vacationImpactPausedLabel(_horas(impacto.pausadas))),
|
||||
],
|
||||
if (impacto.noAfectadas.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.vacationImpactContinuesLabel(_horas(impacto.noAfectadas)),
|
||||
// Issue 1 (feedback-pruebas): a range starts ACTIVE the instant it's
|
||||
// created (today .. today+2), so this hero is the ONLY place a
|
||||
// brand-new range ever renders until it either becomes "programado" in
|
||||
// the future or "pasado" once it ends. Without tap/swipe here, the
|
||||
// very first range a user creates could never be fixed or removed.
|
||||
return Dismissible(
|
||||
key: ValueKey('vacaciones-tarjeta-${rango.id}'),
|
||||
direction: DismissDirection.horizontal,
|
||||
background: const _FondoSwipeEliminarRango(
|
||||
alignment: Alignment.centerLeft,
|
||||
),
|
||||
secondaryBackground: const _FondoSwipeEliminarRango(
|
||||
alignment: Alignment.centerRight,
|
||||
),
|
||||
confirmDismiss: (_) => _confirmarEliminarRango(context, l10n),
|
||||
onDismissed: (_) => estado.eliminarRangoVacaciones(rango.id),
|
||||
child: PluriGlassSurface(
|
||||
glowColor: tokens.electricMagenta.withValues(alpha: 0.24),
|
||||
padding: EdgeInsets.zero,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(tokens.radiusMd),
|
||||
onTap: () => _abrirEditorVacaciones(context, rango: rango),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
localizedVacationName(l10n, rango.nombre),
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Item 21 / audit 9b.4 (t4:454): the "active now" caption is a
|
||||
// teal eyebrow, not default body text.
|
||||
Text(
|
||||
l10n.vacationSummaryActiveCountdown(diasRestantes),
|
||||
style: type.eyebrowLabel.copyWith(
|
||||
color: PluriWaveTokens.brand,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Item 21 / audit 9b.4 (t4:451-462): the screen's signature
|
||||
// element is a start/end date pair joined by a gradient rule —
|
||||
// the prototype never draws a determinate progress bar here.
|
||||
_ParFechasVacaciones(
|
||||
inicio: rango.inicioDia,
|
||||
fin: rango.finDia,
|
||||
destacado: true,
|
||||
reglaKey: const ValueKey('vacaciones-regla-activo'),
|
||||
),
|
||||
if (impacto.pausadas.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.vacationImpactPausedLabel(_horas(impacto.pausadas)),
|
||||
),
|
||||
],
|
||||
if (impacto.noAfectadas.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.vacationImpactContinuesLabel(
|
||||
_horas(impacto.noAfectadas),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -452,61 +552,87 @@ class _TarjetaRangoVacaciones extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final t = context.pluriTokens;
|
||||
final type = context.pluriType;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: t.listSurface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||
final estado = context.read<EstadoAlarmas>();
|
||||
// Issue 1 (feedback-pruebas): tap = edit, swipe = delete (with
|
||||
// confirmation) — same interaction `pantalla_alarmas.dart`'s
|
||||
// `_TarjetaAlarma` already uses for the same concept, applied here to
|
||||
// BOTH the "programados" and "pasados" sections (this card backs both).
|
||||
return Dismissible(
|
||||
key: ValueKey('vacaciones-tarjeta-${rango.id}'),
|
||||
direction: DismissDirection.horizontal,
|
||||
background: const _FondoSwipeEliminarRango(
|
||||
alignment: Alignment.centerLeft,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (encabezado != null) ...[
|
||||
Text(
|
||||
encabezado!,
|
||||
style: type.eyebrowLabel.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 11),
|
||||
],
|
||||
_ParFechasVacaciones(
|
||||
inicio: rango.inicioDia,
|
||||
fin: rango.finDia,
|
||||
destacado: false,
|
||||
reglaKey: ValueKey('vacaciones-regla-${rango.id}'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_outline_rounded,
|
||||
size: 17,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
localizedVacationName(l10n, rango.nombre),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
secondaryBackground: const _FondoSwipeEliminarRango(
|
||||
alignment: Alignment.centerRight,
|
||||
),
|
||||
confirmDismiss: (_) => _confirmarEliminarRango(context, l10n),
|
||||
onDismissed: (_) => estado.eliminarRangoVacaciones(rango.id),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: t.listSurface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: () => _abrirEditorVacaciones(context, rango: rango),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (encabezado != null) ...[
|
||||
Text(
|
||||
encabezado!,
|
||||
style: type.eyebrowLabel.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
const SizedBox(height: 11),
|
||||
],
|
||||
_ParFechasVacaciones(
|
||||
inicio: rango.inicioDia,
|
||||
fin: rango.finDia,
|
||||
destacado: false,
|
||||
reglaKey: ValueKey('vacaciones-regla-${rango.id}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.label_outline_rounded,
|
||||
size: 17,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
localizedVacationName(l10n, rango.nombre),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -609,11 +735,17 @@ class _BloqueFecha extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add-range form. Moved verbatim from `pantalla_alarmas.dart` (WU8's
|
||||
/// Add/edit-range form. Moved verbatim from `pantalla_alarmas.dart` (WU8's
|
||||
/// `_PantallaVacacionesTemporal` used it as a placeholder push target; now
|
||||
/// this screen is the one real consumer). Behaviour unchanged.
|
||||
/// this screen is the one real consumer). Create behaviour unchanged; issue
|
||||
/// 1 (feedback-pruebas) adds the edit half via the optional [rango] — the
|
||||
/// SAME sheet, mirroring `pantalla_alarmas.dart`'s `_EditorAlarmaSheet`
|
||||
/// (`alarma == null` -> create, non-null -> edit; one shared save button
|
||||
/// either way).
|
||||
class _EditorVacacionesSheet extends StatefulWidget {
|
||||
const _EditorVacacionesSheet();
|
||||
const _EditorVacacionesSheet({this.rango});
|
||||
|
||||
final RangoVacaciones? rango;
|
||||
|
||||
@override
|
||||
State<_EditorVacacionesSheet> createState() => _EditorVacacionesSheetState();
|
||||
@@ -629,16 +761,29 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final hoy = DateTime.now();
|
||||
_inicio = DateTime(hoy.year, hoy.month, hoy.day);
|
||||
_fin = _inicio.add(const Duration(days: 2));
|
||||
final rango = widget.rango;
|
||||
if (rango != null) {
|
||||
_inicio = rango.inicioDia;
|
||||
_fin = rango.finDia;
|
||||
} else {
|
||||
final hoy = DateTime.now();
|
||||
_inicio = DateTime(hoy.year, hoy.month, hoy.day);
|
||||
_fin = _inicio.add(const Duration(days: 2));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final rango = widget.rango;
|
||||
_nombreController ??= TextEditingController(
|
||||
text: AppLocalizations.of(context).vacationsDefaultName,
|
||||
text:
|
||||
rango != null
|
||||
? localizedVacationName(
|
||||
AppLocalizations.of(context),
|
||||
rango.nombre,
|
||||
)
|
||||
: AppLocalizations.of(context).vacationsDefaultName,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -662,7 +807,9 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.newVacationRangeTitle,
|
||||
widget.rango != null
|
||||
? l10n.editVacationRangeTitle
|
||||
: l10n.newVacationRangeTitle,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
|
||||
@@ -709,10 +856,16 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
Future<void> _elegirFecha({required bool esInicio}) async {
|
||||
final actual = esInicio ? _inicio : _fin;
|
||||
final hoy = DateTime.now();
|
||||
final hoyDia = DateTime(hoy.year, hoy.month, hoy.day);
|
||||
// Issue 1 (feedback-pruebas): editing a PAST range (reachable from the
|
||||
// "Rangos pasados" section) must not force its dates into the future —
|
||||
// `firstDate` only floors at today for a range that starts there or
|
||||
// later; an already-past range keeps its own start as the floor.
|
||||
final primerDiaPermitido = _inicio.isBefore(hoyDia) ? _inicio : hoyDia;
|
||||
final seleccion = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: actual,
|
||||
firstDate: DateTime(hoy.year, hoy.month, hoy.day),
|
||||
firstDate: primerDiaPermitido,
|
||||
lastDate: hoy.add(const Duration(days: 1460)),
|
||||
);
|
||||
if (seleccion == null) return;
|
||||
@@ -728,12 +881,26 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
||||
|
||||
Future<void> _guardar() async {
|
||||
final estado = context.read<EstadoAlarmas>();
|
||||
final rango = estado.servicio.crearRangoVacaciones(
|
||||
inicio: _inicio,
|
||||
fin: _fin,
|
||||
nombre: _nombreController?.text.trim() ?? '',
|
||||
);
|
||||
await estado.crearRangoVacaciones(rango);
|
||||
final nombre = _nombreController?.text.trim() ?? '';
|
||||
final existente = widget.rango;
|
||||
if (existente != null) {
|
||||
await estado.editarRangoVacaciones(
|
||||
RangoVacaciones(
|
||||
id: existente.id,
|
||||
nombre: nombre,
|
||||
inicio: _inicio,
|
||||
fin: _fin,
|
||||
activo: existente.activo,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final rango = estado.servicio.crearRangoVacaciones(
|
||||
inicio: _inicio,
|
||||
fin: _fin,
|
||||
nombre: nombre,
|
||||
);
|
||||
await estado.crearRangoVacaciones(rango);
|
||||
}
|
||||
if (mounted) Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user