Files
flux/lib/features/tickets/ui/widgets/ticket_list.dart

175 lines
6.3 KiB
Dart
Raw Normal View History

2026-05-15 19:18:03 +02:00
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';
2026-05-16 11:51:26 +02:00
import 'package:flux/features/tickets/blocs/ticket_shipping_cubit.dart';
2026-05-24 12:42:11 +02:00
import 'package:flux/features/tickets/ui/widgets/loan_phone_return_dialog.dart';
2026-05-15 19:18:03 +02:00
import 'package:flux/features/tickets/ui/widgets/ticket_list_card.dart';
2026-05-16 11:51:26 +02:00
import 'package:flux/features/tickets/ui/widgets/ticket_shipping_modal.dart';
2026-05-15 19:18:03 +02:00
class TicketList extends StatelessWidget {
final ScrollController scrollController;
final TicketListState state;
const TicketList({
super.key,
required this.scrollController,
required this.state,
});
2026-05-24 12:42:11 +02:00
void _showShippingModal(BuildContext context) async {
// 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record)
final bool? result = await showModalBottomSheet<bool?>(
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<TicketListCubit>().clearSelection();
// (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB)
context.read<TicketListCubit>().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<String, bool>? loanReturns;
// 2. Se ci sono telefoni in prestito, mostriamo il popup
if (ticketsWithLoans.isNotEmpty) {
loanReturns = await showDialog<Map<String, bool>>(
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<TicketListCubit>().closeTicketsBulk(
ticketIds: ticketIds,
loanReturns: loanReturns, // Può essere null se non c'erano muletti
);
}
}
2026-05-15 19:18:03 +02:00
@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];
2026-05-16 11:51:26 +02:00
final isSelected = state.selectedTickets.contains(ticket);
final isSelectionMode = state.selectedTickets.isNotEmpty;
2026-05-15 19:18:03 +02:00
// 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,
2026-05-16 11:51:26 +02:00
bottom: state.selectedTickets.isNotEmpty
2026-05-15 19:18:03 +02:00
? 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<TicketListCubit>().clearSelection(),
),
Text(
2026-05-16 11:51:26 +02:00
'${state.selectedTickets.length} selezionati',
2026-05-15 19:18:03 +02:00
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const Spacer(),
2026-05-24 12:42:11 +02:00
Row(
children: [
FilledButton.icon(
onPressed: () => _setStatusClosed(context),
icon: const Icon(Icons.approval),
label: const Text('Riconsegna'),
),
const SizedBox(width: 8),
FilledButton.icon(
onPressed: () => _showShippingModal(context),
icon: const Icon(Icons.local_shipping),
label: const Text('Spedisci'),
),
],
2026-05-15 19:18:03 +02:00
),
],
),
),
),
),
],
);
}
}