import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/routes/routes.dart'; import 'package:flux/core/widgets/staff_selector_modal.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.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/models/ticket_model.dart'; import 'package:flux/features/tickets/models/ticket_status_extension.dart'; import 'package:go_router/go_router.dart'; class TicketListScreen extends StatefulWidget { const TicketListScreen({super.key}); @override State createState() => _TicketListScreenState(); } class _TicketListScreenState extends State { final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); // INFINITY SCROLL: Quando arriviamo quasi in fondo, chiediamo altri ticket _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { context.read().loadTickets(); } }); } @override void dispose() { _scrollController.dispose(); _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Assistenza & Riparazioni'), actions: [ // Tasto per filtri avanzati (Data, Staff, Tipo) -> Da fare in un BottomSheet! IconButton( icon: const Icon(Icons.filter_list), onPressed: () { // TODO: Aprire BottomSheet filtri avanzati }, ), ], ), body: Column( children: [ // 1. BARRA DI RICERCA Padding( padding: const EdgeInsets.all(8.0), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Cerca per nome cliente...', prefixIcon: const Icon(Icons.search), suffixIcon: IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); context.read().updateFilters( clearSearch: true, ); }, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), onSubmitted: (value) { context.read().updateFilters( searchTerm: value, ); }, ), ), // 2. FILTRI RAPIDI PER STATO (CHIPS) BlocBuilder( buildWhen: (previous, current) => previous.statusFilter != current.statusFilter, builder: (context, state) { return SizedBox( height: 50, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 8.0), children: [ _buildStatusChip(context, state, null, 'Tutti'), ...TicketStatus.values.map( (status) => _buildStatusChip( context, state, status, status.displayValue, ), ), ], ), ); }, ), const Divider(), // 3. LA LISTA DEI TICKET Expanded( child: BlocBuilder( builder: (context, state) { if (state.isLoading && state.tickets.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (state.tickets.isEmpty) { return const Center(child: Text('Nessun ticket trovato.')); } 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.selectedTicketIds.contains( ticket.id, ); final isSelectionMode = state.selectedTicketIds.isNotEmpty; // Per Desktop mostriamo la checkbox vera e propria final isDesktop = MediaQuery.of(context).size.width > 800; return _TicketCard( 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.selectedTicketIds.isNotEmpty ? 90 : -100, // Nasconde o mostra left: 16, right: 16, child: Card( elevation: 8, color: Theme.of(context).colorScheme.inverseSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), child: Row( children: [ IconButton( icon: const Icon(Icons.close), onPressed: () => context .read() .clearSelection(), ), Text( '${state.selectedTicketIds.length} selezionati', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const Spacer(), // IL NOSTRO FAMOSO BOTTONE SPEDISCI FilledButton.icon( onPressed: () { // Qui lanceremo la modale per creare il DDT // passando la lista: state.selectedTicketIds.toList() print( "Spedisco i ticket: ${state.selectedTicketIds}", ); }, icon: const Icon(Icons.local_shipping), label: const Text('Spedisci'), ), ], ), ), ), ), ], ); }, ), ), ], ), floatingActionButton: FloatingActionButton.extended( icon: const Icon(Icons.add), label: const Text('Nuovo Ticket'), onPressed: () async { StaffMemberModel? createdBy = await getStaffMember(context); if (createdBy == null || !context.mounted) return; await context.pushNamed( Routes.ticketForm, pathParameters: {'id': 'new'}, extra: (createdBy: createdBy, ticket: null), ); if (!context.mounted) return; context.read().loadTickets(refresh: true); }, ), ); } // Widget di supporto per creare le Chip di filtro Widget _buildStatusChip( BuildContext context, TicketListState state, TicketStatus? status, String label, ) { final isSelected = state.statusFilter == status; return Padding( padding: const EdgeInsets.only(right: 8.0), child: ChoiceChip( label: Text(label), selected: isSelected, selectedColor: status?.color.withValues(alpha: 0.2) ?? Colors.blue.withValues(alpha: 0.2), onSelected: (selected) { context.read().updateFilters( statusFilter: selected ? status : null, clearStatus: !selected && status != null, ); }, ), ); } } // --------------------------------------------------------- // LA CARD DEL TICKET (Il "Colpo d'Occhio") // --------------------------------------------------------- class _TicketCard extends StatelessWidget { final TicketModel ticket; final bool isSelected; final bool isSelectionMode; final bool isDesktop; const _TicketCard({ super.key, // <-- Buona pratica aggiungere il super.key required this.ticket, required this.isSelected, required this.isSelectionMode, required this.isDesktop, }); @override Widget build(BuildContext context) { final statusColor = ticket.ticketStatus.color; final statusIcon = ticket.ticketStatus.icon; // Tocco Ninja: Ricaviamo l'iniziale del cliente per l'avatar! final customerName = ticket.customer?.name ?? '?'; final initial = customerName.isNotEmpty ? customerName[0].toUpperCase() : '?'; return Card( // 1. Sfondo leggermente colorato se selezionato color: isSelected ? Colors.blue.withValues(alpha: 0.1) : null, margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), clipBehavior: Clip.antiAlias, child: IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // LA STRISCIA COLORATA LATERALE (Intoccabile, è bellissima) Container(width: 6, color: statusColor), Center( child: Padding( padding: const EdgeInsets.only(left: 16.0), child: isDesktop ? Checkbox( value: isSelected, onChanged: (_) { if (ticket.id != null) { context .read() .toggleTicketSelection(ticket.id!); } }, ) : GestureDetector( onTap: () { if (ticket.id != null) { context .read() .toggleTicketSelection(ticket.id!); } }, child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, animation) => ScaleTransition(scale: animation, child: child), child: isSelected ? const CircleAvatar( key: ValueKey('selected'), backgroundColor: Colors.blue, child: Icon(Icons.check, color: Colors.white), ) : CircleAvatar( key: const ValueKey('unselected'), backgroundColor: Colors.grey.shade200, child: Text( initial, // L'iniziale del cliente! style: TextStyle( color: Colors.grey.shade700, fontWeight: FontWeight.bold, ), ), ), ), ), ), ), Expanded( child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), // ---------------------------------------- title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( ticket.customer?.name ?? 'Cliente Sconosciuto', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), overflow: TextOverflow.ellipsis, ), ), // IL BADGE DELLO STATO Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: statusColor.withValues(alpha: 0.5), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(statusIcon, size: 14, color: statusColor), const SizedBox(width: 4), Text( ticket.ticketStatus.displayValue, style: TextStyle( fontSize: 12, color: statusColor, fontWeight: FontWeight.bold, ), ), ], ), ), ], ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Text( ticket.targetModelName ?? ticket.ticketType.displayValue, style: const TextStyle(fontWeight: FontWeight.w500), ), const SizedBox(height: 4), Text( ticket.createdAt != null ? 'Creato il: ${ticket.createdAt!.day}/${ticket.createdAt!.month}/${ticket.createdAt!.year}' : '', style: TextStyle( color: Colors.grey.shade600, fontSize: 12, ), ), ], ), // --- 3. GESTIONE DEI TAP PER LA SELEZIONE --- onTap: () { if (isSelectionMode) { // Modalità selezione attiva: un tap normale seleziona/deseleziona if (ticket.id != null) { context.read().toggleTicketSelection( ticket.id!, ); } } else { // Modalità normale: entra nel dettaglio del ticket context.pushNamed( Routes.ticketForm, pathParameters: {'id': ticket.id!}, extra: (ticket: ticket, createdBy: null), ); } }, onLongPress: () { // Pressione lunga: forza la selezione (utile per iniziare il workflow) if (!isSelectionMode && ticket.id != null) { context.read().toggleTicketSelection( ticket.id!, ); } }, ), ), ], ), ), ); } }