From a51ac8fe7fd62c25978b2bd07f776886f57afff7 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Tue, 2 Jun 2026 11:52:31 +0200 Subject: [PATCH] ticket refinements --- .../blocs/dashboard_note_list_cubit.dart | 3 +- .../dashboard_store_operation_list_cubit.dart | 2 + .../dashboard_store_ticket_list_cubit.dart | 9 +- .../blocs/dashboard_task_list_cubit.dart | 3 +- .../staff/data/staff_repository.dart | 4 +- .../tickets/blocs/ticket_form_cubit.dart | 18 +++ .../tickets/blocs/ticket_form_state.dart | 11 +- .../tickets/blocs/ticket_list_cubit.dart | 15 ++ .../tickets/ui/ticket_form_screen.dart | 145 ++++++++++-------- .../tickets/ui/widgets/ticket_list.dart | 11 ++ 10 files changed, 155 insertions(+), 66 deletions(-) diff --git a/lib/features/home/dashboard_note_list/blocs/dashboard_note_list_cubit.dart b/lib/features/home/dashboard_note_list/blocs/dashboard_note_list_cubit.dart index cfce3fd..2c9dbac 100644 --- a/lib/features/home/dashboard_note_list/blocs/dashboard_note_list_cubit.dart +++ b/lib/features/home/dashboard_note_list/blocs/dashboard_note_list_cubit.dart @@ -47,7 +47,7 @@ class DashboardNoteListCubit extends Cubit { Future _loadNotesSilently() async { try { final notes = await _repository.getNotes(); - + if (isClosed) return; emit( state.copyWith( status: DashboardNoteListStatus.success, @@ -56,6 +56,7 @@ class DashboardNoteListCubit extends Cubit { ), ); } catch (e) { + if (isClosed) return; emit( state.copyWith( status: DashboardNoteListStatus.failure, diff --git a/lib/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart b/lib/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart index c939a2a..266fa54 100644 --- a/lib/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart +++ b/lib/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart @@ -57,6 +57,7 @@ class DashboardStoreOperationListCubit limit: 10, offset: 0, ); + if (isClosed) return; emit( state.copyWith( status: DashboardStoreOperationListStatus.success, @@ -65,6 +66,7 @@ class DashboardStoreOperationListCubit ), ); } catch (e) { + if (isClosed) return; emit( state.copyWith( status: DashboardStoreOperationListStatus.failure, diff --git a/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart index e6fe6cb..01e27d4 100644 --- a/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart +++ b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart @@ -59,7 +59,7 @@ class DashboardStoreTicketListCubit limit: 10, offset: 0, ); - + if (isClosed) return; emit( state.copyWith( status: DashboardStoreTicketListStatus.success, @@ -68,6 +68,7 @@ class DashboardStoreTicketListCubit ), ); } catch (e) { + if (isClosed) return; emit( state.copyWith( status: DashboardStoreTicketListStatus.failure, @@ -76,4 +77,10 @@ class DashboardStoreTicketListCubit ); } } + + @override + Future close() { + stopListening(); + return super.close(); + } } diff --git a/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart b/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart index fa320f9..1ec6c86 100644 --- a/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart +++ b/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart @@ -53,7 +53,7 @@ class DashboardTaskListCubit extends Cubit { statuses: [TaskStatus.open, TaskStatus.inProgress], limit: 10, ); - + if (isClosed) return; emit( state.copyWith( status: DashboardTaskListStatus.success, @@ -62,6 +62,7 @@ class DashboardTaskListCubit extends Cubit { ), ); } catch (e) { + if (isClosed) return; emit( state.copyWith( status: DashboardTaskListStatus.failure, diff --git a/lib/features/master_data/staff/data/staff_repository.dart b/lib/features/master_data/staff/data/staff_repository.dart index 6067f72..c3cf571 100644 --- a/lib/features/master_data/staff/data/staff_repository.dart +++ b/lib/features/master_data/staff/data/staff_repository.dart @@ -153,10 +153,10 @@ class StaffRepository { // Assegna un membro a un negozio Future assignStaffToStore(String staffId, String storeId) async { - await _supabase.from(Tables.staffInStores).insert({ + await _supabase.from(Tables.staffInStores).upsert({ 'staff_member_id': staffId, 'store_id': storeId, - }); + }, onConflict: 'staff_member_id,store_id'); // Evita duplicati } // Rimuove l'assegnazione diff --git a/lib/features/tickets/blocs/ticket_form_cubit.dart b/lib/features/tickets/blocs/ticket_form_cubit.dart index 8ad9af3..53053ee 100644 --- a/lib/features/tickets/blocs/ticket_form_cubit.dart +++ b/lib/features/tickets/blocs/ticket_form_cubit.dart @@ -367,4 +367,22 @@ class TicketFormCubit extends Cubit { ); } } + + Future deleteTicket() async { + final currentTicket = state.ticket; + + if (currentTicket.id == null || currentTicket.id!.isEmpty) return; + + try { + await _repository.deleteTicket(currentTicket.id!); + emit(state.copyWith(status: TicketFormStatus.deleted)); + } catch (e) { + emit( + state.copyWith( + status: TicketFormStatus.failure, + errorMessage: 'Errore durante l\'eliminazione: $e', + ), + ); + } + } } diff --git a/lib/features/tickets/blocs/ticket_form_state.dart b/lib/features/tickets/blocs/ticket_form_state.dart index 493ff03..cb1197f 100644 --- a/lib/features/tickets/blocs/ticket_form_state.dart +++ b/lib/features/tickets/blocs/ticket_form_state.dart @@ -2,7 +2,16 @@ import 'package:equatable/equatable.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; // Adatta gli import al tuo progetto! -enum TicketFormStatus { initial, ready, loading, saving, success, pop, failure } +enum TicketFormStatus { + initial, + ready, + loading, + saving, + success, + pop, + failure, + deleted, +} class TicketFormState extends Equatable { final TicketModel ticket; diff --git a/lib/features/tickets/blocs/ticket_list_cubit.dart b/lib/features/tickets/blocs/ticket_list_cubit.dart index d97c3f8..10ca700 100644 --- a/lib/features/tickets/blocs/ticket_list_cubit.dart +++ b/lib/features/tickets/blocs/ticket_list_cubit.dart @@ -158,4 +158,19 @@ class TicketListCubit extends Cubit { // Opzionale: Se vuoi comunque riallinearti al server in modo silenzioso dopo l'animazione // loadTickets(refresh: true); } + + Future deleteTickets(List tickets) async { + try { + for (final ticket in tickets) { + await _repository.deleteTicket(ticket.id!); + } + // Rimuoviamo i ticket localmente senza ricaricare tutto + final remainingTickets = state.tickets + .where((t) => !tickets.any((toDelete) => toDelete.id == t.id)) + .toList(); + emit(state.copyWith(tickets: remainingTickets, selectedTickets: {})); + } catch (e) { + emit(state.copyWith(errorMessage: e.toString())); + } + } } diff --git a/lib/features/tickets/ui/ticket_form_screen.dart b/lib/features/tickets/ui/ticket_form_screen.dart index 7f10348..a8ec338 100644 --- a/lib/features/tickets/ui/ticket_form_screen.dart +++ b/lib/features/tickets/ui/ticket_form_screen.dart @@ -348,6 +348,32 @@ class _TicketFormScreenState extends State { trackingCubit.loadTrackings(ticketId, TrackingParentType.ticket); } + void _deleteTicket(TicketModel ticket, {Color color = Colors.red}) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Conferma Cancellazione'), + content: Text( + 'Sei sicuro di voler cancellare il ticket "${ticket.referenceId}"? Questa azione è irreversibile.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Annulla'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: color), + onPressed: () { + context.read().deleteTicket(); + Navigator.of(context).pop(); // Chiude il dialog + }, + child: const Text('Cancella Ticket'), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -359,6 +385,10 @@ class _TicketFormScreenState extends State { _syncTextControllers(state.ticket); } + if (state.status == TicketFormStatus.deleted) { + Navigator.of(context).pop(); + } + if (state.status == TicketFormStatus.success) { context.read().loadTickets(refresh: true); _showSuccessActions( @@ -388,66 +418,61 @@ class _TicketFormScreenState extends State { : 'Modifica Ticket - Operatore: ${state.ticket.createdByName}', ), actions: [ - BlocBuilder( - builder: (context, state) { - final ticket = state.ticket; - - // Se il ticket non è ancora salvato, niente azioni rapide - if (ticket.id == null || ticket.id!.isEmpty) { - return const SizedBox.shrink(); - } - - // CONDIZIONE A: Da iniziare - if (ticket.ticketStatus == TicketStatus.open || - ticket.ticketStatus == TicketStatus.waitingForParts) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: FilledButton.icon( - style: FilledButton.styleFrom( - backgroundColor: - Colors.amber.shade700, // Colore Action - ), - onPressed: () async { - StaffMemberModel? takenBy = await getStaffMember( - context, - ); - if (takenBy == null || !context.mounted) return; - context.read().takeInCharge( - staffId: takenBy.id!, - staffName: takenBy.name, - ); - _navigateToWorkspace(ticket.id!); - }, - icon: const Icon(Icons.play_arrow, color: Colors.white), - label: const Text( - 'Prendi in Carico', - style: TextStyle(color: Colors.white), - ), - ), - ); - } - // CONDIZIONE B: Già in lavorazione - else if (ticket.ticketStatus == TicketStatus.inProgress) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: FilledButton.icon( - onPressed: () => _navigateToWorkspace(ticket.id!), - icon: const Icon(Icons.handyman), - label: const Text('Vai a Lavorazione'), - ), - ); - } - - // Se è chiuso o in altri stati strani, nascondiamo il bottone - return const SizedBox.shrink(); - }, - ), + if (ticket.id != null) ...[ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: FilledButton.icon( + onPressed: () => _deleteTicket(ticket, color: Colors.red), + icon: const Icon(Icons.delete), + label: const Text('Cancella Ticket'), + ), + ), + ], + if (ticket.ticketStatus == TicketStatus.open || + ticket.ticketStatus == TicketStatus.waitingForParts) ...[ + // CONDIZIONE A: Da iniziare + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: FilledButton.icon( + style: FilledButton.styleFrom( + backgroundColor: Colors.amber.shade700, // Colore Action + ), + onPressed: () async { + StaffMemberModel? takenBy = await getStaffMember(context); + if (takenBy == null || !context.mounted) return; + context.read().takeInCharge( + staffId: takenBy.id!, + staffName: takenBy.name, + ); + _navigateToWorkspace(ticket.id!); + }, + icon: const Icon(Icons.play_arrow, color: Colors.white), + label: const Text( + 'Prendi in Carico', + style: TextStyle(color: Colors.white), + ), + ), + ), + ], + if (ticket.ticketStatus == TicketStatus.inProgress) ...[ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: FilledButton.icon( + onPressed: () => _navigateToWorkspace(ticket.id!), + icon: const Icon(Icons.handyman), + label: const Text('Vai a Lavorazione'), + ), + ), + ], Padding( padding: const EdgeInsets.only(right: 16.0), child: Chip( diff --git a/lib/features/tickets/ui/widgets/ticket_list.dart b/lib/features/tickets/ui/widgets/ticket_list.dart index 50db9e6..d085649 100644 --- a/lib/features/tickets/ui/widgets/ticket_list.dart +++ b/lib/features/tickets/ui/widgets/ticket_list.dart @@ -78,6 +78,12 @@ class TicketList extends StatelessWidget { } } + void _deleteTickets(BuildContext context) { + context.read().deleteTickets( + state.selectedTickets.toList(), + ); + } + @override Widget build(BuildContext context) { return Stack( @@ -178,6 +184,11 @@ class TicketList extends StatelessWidget { runSpacing: 8.0, alignment: WrapAlignment.end, children: [ + IconButton.filled( + tooltip: 'Elimina', + onPressed: () => _deleteTickets(context), + icon: const Icon(Icons.delete), + ), IconButton.filled( tooltip: 'Riconsegna', onPressed: () => _setStatusClosed(context),