import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_list_state.dart'; import 'package:flux/features/tickets/blocs/ticket_shipping_cubit.dart'; import 'package:flux/features/tickets/ui/widgets/loan_phone_return_dialog.dart'; import 'package:flux/features/tickets/ui/widgets/ticket_list_card.dart'; import 'package:flux/features/tickets/ui/widgets/ticket_shipping_modal.dart'; class TicketList extends StatelessWidget { final ScrollController scrollController; final TicketListState state; const TicketList({ super.key, required this.scrollController, required this.state, }); void _showShippingModal(BuildContext context) async { // 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record) final bool? result = await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) { return BlocProvider( create: (context) => TicketShippingCubit(tickets: state.selectedTickets.toList()) ..loadRepairCenters(), child: TicketShippingModal( ticketIds: state.selectedTickets.map((t) => t.id!).toList(), ), ); }, ); // 2. Se l'utente ha chiuso trascinando giù, result è null. // Se ha salvato con successo, result contiene il nostro Record! if (result != null && context.mounted) { // 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista context.read().clearSelection(); // (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB) context.read().loadTickets(refresh: true); } } void _setStatusClosed(BuildContext context) async { // 1. Filtriamo solo i ticket che hanno un telefono in prestito final ticketsWithLoans = state.selectedTickets .where((t) => t.hasCourtesyDevice == true) .toList(); // Prepariamo la variabile per contenere i telefoni restituiti (se ce ne sono) Map? loanReturns; // 2. Se ci sono telefoni in prestito, mostriamo il popup if (ticketsWithLoans.isNotEmpty) { loanReturns = await showDialog>( context: context, builder: (context) => LoanPhoneReturnDialog(ticketsWithLoans: ticketsWithLoans), ); // Se l'utente ha premuto fuori o ha fatto "Annulla", blocchiamo l'operazione bulk if (loanReturns == null) return; } // 3. Se siamo qui, o non c'erano muletti, o l'utente ha confermato il popup. // Lanciamo l'azione sul Cubit! (Dovrai creare/adattare questo metodo nel tuo Cubit) if (context.mounted) { final ticketIds = state.selectedTickets.map((t) => t.id!).toList(); // Passiamo gli ID dei ticket da chiudere e la mappa delle restituzioni context.read().closeTicketsBulk( ticketIds: ticketIds, loanReturns: loanReturns, // Può essere null se non c'erano muletti ); } } void _deleteTickets(BuildContext context) { context.read().deleteTickets( state.selectedTickets.toList(), ); } @override Widget build(BuildContext context) { return Stack( children: [ ListView.builder( controller: scrollController, itemCount: state.hasReachedMax ? state.tickets.length : state.tickets.length + 1, itemBuilder: (context, index) { // Se siamo all'ultimo elemento e non abbiamo raggiunto il max, mostriamo il loader if (index >= state.tickets.length) { return const Center( child: Padding( padding: EdgeInsets.all(16.0), child: CircularProgressIndicator(), ), ); } final ticket = state.tickets[index]; final isSelected = state.selectedTickets.contains(ticket); final isSelectionMode = state.selectedTickets.isNotEmpty; // Per Desktop mostriamo la checkbox vera e propria final isDesktop = MediaQuery.of(context).size.width > 800; return TicketListCard( ticket: ticket, isSelected: isSelected, isSelectionMode: isSelectionMode, isDesktop: isDesktop, ); }, ), // 2. LA BARRA DELLE AZIONI BULK (Appare magicamente dal basso) AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, bottom: state.selectedTickets.isNotEmpty ? 90 : -100, // Mettiamo left e right a 0 per far occupare tutta la larghezza invisibile left: 0, right: 0, child: Align( alignment: Alignment.bottomCenter, // 1. IL LIMITE MASSIMO: Su desktop non supererà mai i 600px, su mobile si restringe da solo child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 600), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Card( elevation: 8, color: Theme.of(context).colorScheme.inverseSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 16, ), // Qui possiamo giocare coi bordi ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), // 2. LA ROW PRINCIPALE: Spinge tutto ai due estremi del nostro "dock" child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // BLOCCO SINISTRO: Chiusura e Contatore Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.close), color: Theme.of( context, ).colorScheme.onInverseSurface, onPressed: () => context .read() .clearSelection(), ), const SizedBox(width: 8), Text( '${state.selectedTickets.length} selezionati', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Theme.of( context, ).colorScheme.onInverseSurface, ), ), ], ), // BLOCCO DESTRO: Wrap confinato solo ai bottoni Wrap( spacing: 8.0, 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), icon: const Icon(Icons.approval), ), IconButton.filled( tooltip: 'Spedisci', onPressed: () => _showShippingModal(context), icon: const Icon(Icons.local_shipping), ), ], ), ], ), ), ), ), ), ), ), ], ); } }