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);
|
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) ──────────────────────────────
|
// ── Vacation queries (design ADR-6, WU9) ──────────────────────────────
|
||||||
// Four PURE queries: none writes, none reschedules, none touches the
|
// Four PURE queries: none writes, none reschedules, none touches the
|
||||||
// native bridge. Read-only over `_alarmas`/`_vacaciones`. Every method
|
// native bridge. Read-only over `_alarmas`/`_vacaciones`. Every method
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "حذف النطاق",
|
"deleteRangeTooltip": "حذف النطاق",
|
||||||
"vacationsDefaultName": "إجازات",
|
"vacationsDefaultName": "إجازات",
|
||||||
"newVacationRangeTitle": "نطاق إجازة جديد",
|
"newVacationRangeTitle": "نطاق إجازة جديد",
|
||||||
|
"editVacationRangeTitle": "تعديل نطاق الإجازة",
|
||||||
|
"vacationDeleteConfirmTitle": "هل تريد حذف نطاق الإجازة؟",
|
||||||
|
"vacationDeleteConfirmMessage": "لا يمكن التراجع عن هذا الإجراء.",
|
||||||
"startField": "البداية",
|
"startField": "البداية",
|
||||||
"endField": "النهاية",
|
"endField": "النهاية",
|
||||||
"saveRangeAction": "حفظ النطاق",
|
"saveRangeAction": "حفظ النطاق",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "পরিসর মুছুন",
|
"deleteRangeTooltip": "পরিসর মুছুন",
|
||||||
"vacationsDefaultName": "ছুটি",
|
"vacationsDefaultName": "ছুটি",
|
||||||
"newVacationRangeTitle": "নতুন ছুটির পরিসর",
|
"newVacationRangeTitle": "নতুন ছুটির পরিসর",
|
||||||
|
"editVacationRangeTitle": "ছুটির পরিসর সম্পাদনা করুন",
|
||||||
|
"vacationDeleteConfirmTitle": "ছুটির পরিসর মুছবেন?",
|
||||||
|
"vacationDeleteConfirmMessage": "এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।",
|
||||||
"startField": "শুরু",
|
"startField": "শুরু",
|
||||||
"endField": "শেষ",
|
"endField": "শেষ",
|
||||||
"saveRangeAction": "পরিসর সংরক্ষণ করুন",
|
"saveRangeAction": "পরিসর সংরক্ষণ করুন",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Zeitraum löschen",
|
"deleteRangeTooltip": "Zeitraum löschen",
|
||||||
"vacationsDefaultName": "Ferien",
|
"vacationsDefaultName": "Ferien",
|
||||||
"newVacationRangeTitle": "Neuer Ferienzeitraum",
|
"newVacationRangeTitle": "Neuer Ferienzeitraum",
|
||||||
|
"editVacationRangeTitle": "Ferienzeitraum bearbeiten",
|
||||||
|
"vacationDeleteConfirmTitle": "Ferienzeitraum löschen?",
|
||||||
|
"vacationDeleteConfirmMessage": "Dies kann nicht rückgängig gemacht werden.",
|
||||||
"startField": "Beginn",
|
"startField": "Beginn",
|
||||||
"endField": "Ende",
|
"endField": "Ende",
|
||||||
"saveRangeAction": "Zeitraum speichern",
|
"saveRangeAction": "Zeitraum speichern",
|
||||||
|
|||||||
@@ -580,6 +580,9 @@
|
|||||||
"vacationNoActiveRangeHint": "No active vacation range right now.",
|
"vacationNoActiveRangeHint": "No active vacation range right now.",
|
||||||
"vacationsDefaultName": "Vacation",
|
"vacationsDefaultName": "Vacation",
|
||||||
"newVacationRangeTitle": "New vacation range",
|
"newVacationRangeTitle": "New vacation range",
|
||||||
|
"editVacationRangeTitle": "Edit vacation range",
|
||||||
|
"vacationDeleteConfirmTitle": "Delete vacation range?",
|
||||||
|
"vacationDeleteConfirmMessage": "This can't be undone.",
|
||||||
"startField": "Start",
|
"startField": "Start",
|
||||||
"endField": "End",
|
"endField": "End",
|
||||||
"saveRangeAction": "Save range",
|
"saveRangeAction": "Save range",
|
||||||
|
|||||||
@@ -580,6 +580,9 @@
|
|||||||
"vacationNoActiveRangeHint": "No hay un rango de vacaciones activo ahora mismo.",
|
"vacationNoActiveRangeHint": "No hay un rango de vacaciones activo ahora mismo.",
|
||||||
"vacationsDefaultName": "Vacaciones",
|
"vacationsDefaultName": "Vacaciones",
|
||||||
"newVacationRangeTitle": "Nuevo rango de 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",
|
"startField": "Inicio",
|
||||||
"endField": "Fin",
|
"endField": "Fin",
|
||||||
"saveRangeAction": "Guardar rango",
|
"saveRangeAction": "Guardar rango",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Supprimer la période",
|
"deleteRangeTooltip": "Supprimer la période",
|
||||||
"vacationsDefaultName": "Vacances",
|
"vacationsDefaultName": "Vacances",
|
||||||
"newVacationRangeTitle": "Nouvelle période de 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",
|
"startField": "Début",
|
||||||
"endField": "Fin",
|
"endField": "Fin",
|
||||||
"saveRangeAction": "Enregistrer la période",
|
"saveRangeAction": "Enregistrer la période",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "अवधि हटाएँ",
|
"deleteRangeTooltip": "अवधि हटाएँ",
|
||||||
"vacationsDefaultName": "छुट्टियाँ",
|
"vacationsDefaultName": "छुट्टियाँ",
|
||||||
"newVacationRangeTitle": "नई छुट्टी अवधि",
|
"newVacationRangeTitle": "नई छुट्टी अवधि",
|
||||||
|
"editVacationRangeTitle": "छुट्टी अवधि संपादित करें",
|
||||||
|
"vacationDeleteConfirmTitle": "छुट्टी अवधि हटाएं?",
|
||||||
|
"vacationDeleteConfirmMessage": "इसे वापस नहीं लिया जा सकता।",
|
||||||
"startField": "शुरुआत",
|
"startField": "शुरुआत",
|
||||||
"endField": "समाप्ति",
|
"endField": "समाप्ति",
|
||||||
"saveRangeAction": "अवधि सहेजें",
|
"saveRangeAction": "अवधि सहेजें",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Hapus rentang",
|
"deleteRangeTooltip": "Hapus rentang",
|
||||||
"vacationsDefaultName": "Liburan",
|
"vacationsDefaultName": "Liburan",
|
||||||
"newVacationRangeTitle": "Rentang liburan baru",
|
"newVacationRangeTitle": "Rentang liburan baru",
|
||||||
|
"editVacationRangeTitle": "Edit rentang liburan",
|
||||||
|
"vacationDeleteConfirmTitle": "Hapus rentang liburan?",
|
||||||
|
"vacationDeleteConfirmMessage": "Tindakan ini tidak dapat dibatalkan.",
|
||||||
"startField": "Mulai",
|
"startField": "Mulai",
|
||||||
"endField": "Akhir",
|
"endField": "Akhir",
|
||||||
"saveRangeAction": "Simpan rentang",
|
"saveRangeAction": "Simpan rentang",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Elimina periodo",
|
"deleteRangeTooltip": "Elimina periodo",
|
||||||
"vacationsDefaultName": "Vacanze",
|
"vacationsDefaultName": "Vacanze",
|
||||||
"newVacationRangeTitle": "Nuovo periodo di vacanza",
|
"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",
|
"startField": "Inizio",
|
||||||
"endField": "Fine",
|
"endField": "Fine",
|
||||||
"saveRangeAction": "Salva periodo",
|
"saveRangeAction": "Salva periodo",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "期間を削除",
|
"deleteRangeTooltip": "期間を削除",
|
||||||
"vacationsDefaultName": "休暇",
|
"vacationsDefaultName": "休暇",
|
||||||
"newVacationRangeTitle": "新しい休暇期間",
|
"newVacationRangeTitle": "新しい休暇期間",
|
||||||
|
"editVacationRangeTitle": "休暇期間を編集",
|
||||||
|
"vacationDeleteConfirmTitle": "休暇期間を削除しますか?",
|
||||||
|
"vacationDeleteConfirmMessage": "この操作は元に戻せません。",
|
||||||
"startField": "開始",
|
"startField": "開始",
|
||||||
"endField": "終了",
|
"endField": "終了",
|
||||||
"saveRangeAction": "期間を保存",
|
"saveRangeAction": "期間を保存",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Excluir período",
|
"deleteRangeTooltip": "Excluir período",
|
||||||
"vacationsDefaultName": "Férias",
|
"vacationsDefaultName": "Férias",
|
||||||
"newVacationRangeTitle": "Novo período de 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",
|
"startField": "Início",
|
||||||
"endField": "Fim",
|
"endField": "Fim",
|
||||||
"saveRangeAction": "Salvar período",
|
"saveRangeAction": "Salvar período",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "Удалить период",
|
"deleteRangeTooltip": "Удалить период",
|
||||||
"vacationsDefaultName": "Отпуск",
|
"vacationsDefaultName": "Отпуск",
|
||||||
"newVacationRangeTitle": "Новый период отпуска",
|
"newVacationRangeTitle": "Новый период отпуска",
|
||||||
|
"editVacationRangeTitle": "Изменить период отпуска",
|
||||||
|
"vacationDeleteConfirmTitle": "Удалить период отпуска?",
|
||||||
|
"vacationDeleteConfirmMessage": "Это действие нельзя отменить.",
|
||||||
"startField": "Начало",
|
"startField": "Начало",
|
||||||
"endField": "Конец",
|
"endField": "Конец",
|
||||||
"saveRangeAction": "Сохранить период",
|
"saveRangeAction": "Сохранить период",
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
"deleteRangeTooltip": "删除范围",
|
"deleteRangeTooltip": "删除范围",
|
||||||
"vacationsDefaultName": "假期",
|
"vacationsDefaultName": "假期",
|
||||||
"newVacationRangeTitle": "新的假期范围",
|
"newVacationRangeTitle": "新的假期范围",
|
||||||
|
"editVacationRangeTitle": "编辑假期范围",
|
||||||
|
"vacationDeleteConfirmTitle": "删除假期范围?",
|
||||||
|
"vacationDeleteConfirmMessage": "此操作无法撤销。",
|
||||||
"startField": "开始",
|
"startField": "开始",
|
||||||
"endField": "结束",
|
"endField": "结束",
|
||||||
"saveRangeAction": "保存范围",
|
"saveRangeAction": "保存范围",
|
||||||
|
|||||||
@@ -2078,6 +2078,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'Nuevo rango de vacaciones'**
|
/// **'Nuevo rango de vacaciones'**
|
||||||
String get newVacationRangeTitle;
|
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.
|
/// No description provided for @startField.
|
||||||
///
|
///
|
||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
|
|||||||
@@ -1133,6 +1133,15 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'نطاق إجازة جديد';
|
String get newVacationRangeTitle => 'نطاق إجازة جديد';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => 'تعديل نطاق الإجازة';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => 'هل تريد حذف نطاق الإجازة؟';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage => 'لا يمكن التراجع عن هذا الإجراء.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => 'البداية';
|
String get startField => 'البداية';
|
||||||
|
|
||||||
|
|||||||
@@ -1140,6 +1140,16 @@ class AppLocalizationsBn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'নতুন ছুটির পরিসর';
|
String get newVacationRangeTitle => 'নতুন ছুটির পরিসর';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => 'ছুটির পরিসর সম্পাদনা করুন';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => 'ছুটির পরিসর মুছবেন?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage =>
|
||||||
|
'এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => 'শুরু';
|
String get startField => 'শুরু';
|
||||||
|
|
||||||
|
|||||||
@@ -1142,6 +1142,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Neuer Ferienzeitraum';
|
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
|
@override
|
||||||
String get startField => 'Beginn';
|
String get startField => 'Beginn';
|
||||||
|
|
||||||
|
|||||||
@@ -1133,6 +1133,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'New vacation range';
|
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
|
@override
|
||||||
String get startField => 'Start';
|
String get startField => 'Start';
|
||||||
|
|
||||||
|
|||||||
@@ -1139,6 +1139,16 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Nuevo rango de vacaciones';
|
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
|
@override
|
||||||
String get startField => 'Inicio';
|
String get startField => 'Inicio';
|
||||||
|
|
||||||
|
|||||||
@@ -1145,6 +1145,15 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Nouvelle période de vacances';
|
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
|
@override
|
||||||
String get startField => 'Début';
|
String get startField => 'Début';
|
||||||
|
|
||||||
|
|||||||
@@ -1134,6 +1134,15 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'नई छुट्टी अवधि';
|
String get newVacationRangeTitle => 'नई छुट्टी अवधि';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => 'छुट्टी अवधि संपादित करें';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => 'छुट्टी अवधि हटाएं?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage => 'इसे वापस नहीं लिया जा सकता।';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => 'शुरुआत';
|
String get startField => 'शुरुआत';
|
||||||
|
|
||||||
|
|||||||
@@ -1139,6 +1139,16 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Rentang liburan baru';
|
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
|
@override
|
||||||
String get startField => 'Mulai';
|
String get startField => 'Mulai';
|
||||||
|
|
||||||
|
|||||||
@@ -1144,6 +1144,16 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Nuovo periodo di vacanza';
|
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
|
@override
|
||||||
String get startField => 'Inizio';
|
String get startField => 'Inizio';
|
||||||
|
|
||||||
|
|||||||
@@ -1101,6 +1101,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => '新しい休暇期間';
|
String get newVacationRangeTitle => '新しい休暇期間';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => '休暇期間を編集';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => '休暇期間を削除しますか?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage => 'この操作は元に戻せません。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => '開始';
|
String get startField => '開始';
|
||||||
|
|
||||||
|
|||||||
@@ -1138,6 +1138,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Novo período de férias';
|
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
|
@override
|
||||||
String get startField => 'Início';
|
String get startField => 'Início';
|
||||||
|
|
||||||
|
|||||||
@@ -1140,6 +1140,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => 'Новый период отпуска';
|
String get newVacationRangeTitle => 'Новый период отпуска';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => 'Изменить период отпуска';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => 'Удалить период отпуска?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage => 'Это действие нельзя отменить.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => 'Начало';
|
String get startField => 'Начало';
|
||||||
|
|
||||||
|
|||||||
@@ -1097,6 +1097,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newVacationRangeTitle => '新的假期范围';
|
String get newVacationRangeTitle => '新的假期范围';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editVacationRangeTitle => '编辑假期范围';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmTitle => '删除假期范围?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vacationDeleteConfirmMessage => '此操作无法撤销。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get startField => '开始';
|
String get startField => '开始';
|
||||||
|
|
||||||
|
|||||||
@@ -82,13 +82,78 @@ class PantallaVacaciones extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _abrirAlta(BuildContext context) async {
|
Future<void> _abrirAlta(BuildContext context) =>
|
||||||
await showModalBottomSheet<void>(
|
_abrirEditorVacaciones(context);
|
||||||
context: context,
|
}
|
||||||
isScrollControlled: true,
|
|
||||||
useSafeArea: true,
|
/// Issue 1 (feedback-pruebas): the ONE sheet-opener both the header/CTA
|
||||||
backgroundColor: Colors.transparent,
|
/// "create" entry points and every range's own "tap to edit" affordance call
|
||||||
builder: (_) => const _EditorVacacionesSheet(),
|
/// -- 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 diasRestantes = rango.finDia.difference(hoyDia).inDays;
|
||||||
final impacto = estado.impactoDeRango(rango);
|
final impacto = estado.impactoDeRango(rango);
|
||||||
final type = context.pluriType;
|
final type = context.pluriType;
|
||||||
|
final tokens = context.pluriTokens;
|
||||||
|
|
||||||
return PluriGlassSurface(
|
// Issue 1 (feedback-pruebas): a range starts ACTIVE the instant it's
|
||||||
glowColor: context.pluriTokens.electricMagenta.withValues(alpha: 0.24),
|
// created (today .. today+2), so this hero is the ONLY place a
|
||||||
child: Column(
|
// brand-new range ever renders until it either becomes "programado" in
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// the future or "pasado" once it ends. Without tap/swipe here, the
|
||||||
children: [
|
// very first range a user creates could never be fixed or removed.
|
||||||
Text(
|
return Dismissible(
|
||||||
localizedVacationName(l10n, rango.nombre),
|
key: ValueKey('vacaciones-tarjeta-${rango.id}'),
|
||||||
style: Theme.of(
|
direction: DismissDirection.horizontal,
|
||||||
context,
|
background: const _FondoSwipeEliminarRango(
|
||||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
|
alignment: Alignment.centerLeft,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
secondaryBackground: const _FondoSwipeEliminarRango(
|
||||||
// Item 21 / audit 9b.4 (t4:454): the "active now" caption is a
|
alignment: Alignment.centerRight,
|
||||||
// teal eyebrow, not default body text.
|
),
|
||||||
Text(
|
confirmDismiss: (_) => _confirmarEliminarRango(context, l10n),
|
||||||
l10n.vacationSummaryActiveCountdown(diasRestantes),
|
onDismissed: (_) => estado.eliminarRangoVacaciones(rango.id),
|
||||||
style: type.eyebrowLabel.copyWith(color: PluriWaveTokens.brand),
|
child: PluriGlassSurface(
|
||||||
),
|
glowColor: tokens.electricMagenta.withValues(alpha: 0.24),
|
||||||
const SizedBox(height: 12),
|
padding: EdgeInsets.zero,
|
||||||
// Item 21 / audit 9b.4 (t4:451-462): the screen's signature
|
child: Material(
|
||||||
// element is a start/end date pair joined by a gradient rule —
|
type: MaterialType.transparency,
|
||||||
// the prototype never draws a determinate progress bar here.
|
child: InkWell(
|
||||||
_ParFechasVacaciones(
|
borderRadius: BorderRadius.circular(tokens.radiusMd),
|
||||||
inicio: rango.inicioDia,
|
onTap: () => _abrirEditorVacaciones(context, rango: rango),
|
||||||
fin: rango.finDia,
|
child: Padding(
|
||||||
destacado: true,
|
padding: const EdgeInsets.all(16),
|
||||||
reglaKey: const ValueKey('vacaciones-regla-activo'),
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (impacto.pausadas.isNotEmpty) ...[
|
children: [
|
||||||
const SizedBox(height: 12),
|
Text(
|
||||||
Text(l10n.vacationImpactPausedLabel(_horas(impacto.pausadas))),
|
localizedVacationName(l10n, rango.nombre),
|
||||||
],
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
if (impacto.noAfectadas.isNotEmpty) ...[
|
fontWeight: FontWeight.w900,
|
||||||
const SizedBox(height: 4),
|
),
|
||||||
Text(
|
),
|
||||||
l10n.vacationImpactContinuesLabel(_horas(impacto.noAfectadas)),
|
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 l10n = AppLocalizations.of(context);
|
||||||
final t = context.pluriTokens;
|
final t = context.pluriTokens;
|
||||||
final type = context.pluriType;
|
final type = context.pluriType;
|
||||||
return DecoratedBox(
|
final estado = context.read<EstadoAlarmas>();
|
||||||
decoration: BoxDecoration(
|
// Issue 1 (feedback-pruebas): tap = edit, swipe = delete (with
|
||||||
color: t.listSurface,
|
// confirmation) — same interaction `pantalla_alarmas.dart`'s
|
||||||
borderRadius: BorderRadius.circular(20),
|
// `_TarjetaAlarma` already uses for the same concept, applied here to
|
||||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
// 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(
|
secondaryBackground: const _FondoSwipeEliminarRango(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
alignment: Alignment.centerRight,
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
confirmDismiss: (_) => _confirmarEliminarRango(context, l10n),
|
||||||
children: [
|
onDismissed: (_) => estado.eliminarRangoVacaciones(rango.id),
|
||||||
if (encabezado != null) ...[
|
child: DecoratedBox(
|
||||||
Text(
|
decoration: BoxDecoration(
|
||||||
encabezado!,
|
color: t.listSurface,
|
||||||
style: type.eyebrowLabel.copyWith(
|
borderRadius: BorderRadius.circular(20),
|
||||||
color: Theme.of(
|
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||||
context,
|
),
|
||||||
).colorScheme.onSurface.withValues(alpha: 0.5),
|
child: Material(
|
||||||
),
|
type: MaterialType.transparency,
|
||||||
),
|
child: InkWell(
|
||||||
const SizedBox(height: 11),
|
borderRadius: BorderRadius.circular(20),
|
||||||
],
|
onTap: () => _abrirEditorVacaciones(context, rango: rango),
|
||||||
_ParFechasVacaciones(
|
child: Padding(
|
||||||
inicio: rango.inicioDia,
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
||||||
fin: rango.finDia,
|
child: Column(
|
||||||
destacado: false,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
reglaKey: ValueKey('vacaciones-regla-${rango.id}'),
|
children: [
|
||||||
),
|
if (encabezado != null) ...[
|
||||||
const SizedBox(height: 12),
|
Text(
|
||||||
Row(
|
encabezado!,
|
||||||
children: [
|
style: type.eyebrowLabel.copyWith(
|
||||||
Icon(
|
color: Theme.of(
|
||||||
Icons.label_outline_rounded,
|
context,
|
||||||
size: 17,
|
).colorScheme.onSurface.withValues(alpha: 0.5),
|
||||||
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,
|
const SizedBox(height: 11),
|
||||||
overflow: TextOverflow.ellipsis,
|
],
|
||||||
|
_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
|
/// `_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 {
|
class _EditorVacacionesSheet extends StatefulWidget {
|
||||||
const _EditorVacacionesSheet();
|
const _EditorVacacionesSheet({this.rango});
|
||||||
|
|
||||||
|
final RangoVacaciones? rango;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_EditorVacacionesSheet> createState() => _EditorVacacionesSheetState();
|
State<_EditorVacacionesSheet> createState() => _EditorVacacionesSheetState();
|
||||||
@@ -629,16 +761,29 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final hoy = DateTime.now();
|
final rango = widget.rango;
|
||||||
_inicio = DateTime(hoy.year, hoy.month, hoy.day);
|
if (rango != null) {
|
||||||
_fin = _inicio.add(const Duration(days: 2));
|
_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
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
final rango = widget.rango;
|
||||||
_nombreController ??= TextEditingController(
|
_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,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
l10n.newVacationRangeTitle,
|
widget.rango != null
|
||||||
|
? l10n.editVacationRangeTitle
|
||||||
|
: l10n.newVacationRangeTitle,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
|
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w900),
|
||||||
@@ -709,10 +856,16 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
|||||||
Future<void> _elegirFecha({required bool esInicio}) async {
|
Future<void> _elegirFecha({required bool esInicio}) async {
|
||||||
final actual = esInicio ? _inicio : _fin;
|
final actual = esInicio ? _inicio : _fin;
|
||||||
final hoy = DateTime.now();
|
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(
|
final seleccion = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: actual,
|
initialDate: actual,
|
||||||
firstDate: DateTime(hoy.year, hoy.month, hoy.day),
|
firstDate: primerDiaPermitido,
|
||||||
lastDate: hoy.add(const Duration(days: 1460)),
|
lastDate: hoy.add(const Duration(days: 1460)),
|
||||||
);
|
);
|
||||||
if (seleccion == null) return;
|
if (seleccion == null) return;
|
||||||
@@ -728,12 +881,26 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> {
|
|||||||
|
|
||||||
Future<void> _guardar() async {
|
Future<void> _guardar() async {
|
||||||
final estado = context.read<EstadoAlarmas>();
|
final estado = context.read<EstadoAlarmas>();
|
||||||
final rango = estado.servicio.crearRangoVacaciones(
|
final nombre = _nombreController?.text.trim() ?? '';
|
||||||
inicio: _inicio,
|
final existente = widget.rango;
|
||||||
fin: _fin,
|
if (existente != null) {
|
||||||
nombre: _nombreController?.text.trim() ?? '',
|
await estado.editarRangoVacaciones(
|
||||||
);
|
RangoVacaciones(
|
||||||
await estado.crearRangoVacaciones(rango);
|
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);
|
if (mounted) Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,4 +445,173 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('issue 1 (feedback-pruebas): editar y eliminar rangos', () {
|
||||||
|
testWidgets(
|
||||||
|
'tocar la tarjeta de un rango programado abre el editor precargado '
|
||||||
|
'con su nombre y fechas',
|
||||||
|
(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);
|
||||||
|
|
||||||
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
||||||
|
// Scoped to the TextField specifically -- the original card's OWN
|
||||||
|
// "Verano" label is still (offstage, behind the modal) in the tree,
|
||||||
|
// so a bare `find.text('Verano')` would ambiguously match both.
|
||||||
|
expect(find.widgetWithText(TextField, 'Verano'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'guardar el editor abierto por tap actualiza el rango existente (no '
|
||||||
|
'crea uno nuevo)',
|
||||||
|
(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.enterText(find.byType(TextField), 'Verano renombrado');
|
||||||
|
final boton = tester.widget<FilledButton>(
|
||||||
|
find.widgetWithText(FilledButton, l10n.saveRangeAction),
|
||||||
|
);
|
||||||
|
boton.onPressed!();
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(estado.vacaciones, hasLength(1));
|
||||||
|
expect(estado.vacaciones.single.id, 'f2');
|
||||||
|
expect(estado.vacaciones.single.nombre, 'Verano renombrado');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'deslizar la tarjeta de un rango pide confirmacion; cancelar la '
|
||||||
|
'conserva y confirmar la elimina',
|
||||||
|
(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)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cancelar: el rango se conserva.
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const ValueKey('vacaciones-tarjeta-f2')),
|
||||||
|
const Offset(-600, 0),
|
||||||
|
);
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
||||||
|
await tester.tap(find.text(l10n.cancelAction));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(estado.vacaciones, hasLength(1));
|
||||||
|
|
||||||
|
// Confirmar: el rango se elimina.
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const ValueKey('vacaciones-tarjeta-f2')),
|
||||||
|
const Offset(-600, 0),
|
||||||
|
);
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
||||||
|
await tester.tap(find.text(l10n.deleteAction));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(estado.vacaciones, isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'el rango ACTIVO (mostrado en el hero) tambien se puede editar (tap) '
|
||||||
|
'y eliminar (swipe) -- un rango recien creado siempre esta activo y '
|
||||||
|
'nunca aparece en las listas programado/pasado',
|
||||||
|
(tester) async {
|
||||||
|
final estado = await _crearEstado(
|
||||||
|
vacaciones: [
|
||||||
|
RangoVacaciones(
|
||||||
|
id: 'v1',
|
||||||
|
nombre: 'Julio activo',
|
||||||
|
inicio: _hoyDia.subtract(const Duration(days: 3)),
|
||||||
|
fin: _hoyDia.add(const Duration(days: 5)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
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-v1')));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
||||||
|
// Scoped to the TextField specifically -- the hero's OWN "Julio
|
||||||
|
// activo" label is still (offstage, behind the modal) in the tree.
|
||||||
|
expect(find.widgetWithText(TextField, 'Julio activo'), findsOneWidget);
|
||||||
|
// Dismiss the editor sheet (no explicit close button -- same as the
|
||||||
|
// pre-existing "Anadir rango" sheet, dismissible via the standard
|
||||||
|
// modal-bottom-sheet Navigator.pop) before interacting with the
|
||||||
|
// list underneath it.
|
||||||
|
Navigator.of(tester.element(find.byType(PantallaVacaciones))).pop();
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const ValueKey('vacaciones-tarjeta-v1')),
|
||||||
|
const Offset(-600, 0),
|
||||||
|
);
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
await tester.tap(find.text(l10n.deleteAction));
|
||||||
|
await _pumpEstable(tester);
|
||||||
|
|
||||||
|
expect(estado.vacaciones, isEmpty);
|
||||||
|
expect(find.text(l10n.vacationNoActiveRangeHint), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user