2026-05-07 16:28:01 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
|
|
|
|
import 'package:flux/features/tickets/data/ticket_repository.dart';
|
2026-05-24 12:42:11 +02:00
|
|
|
import 'package:flux/features/tracking/data/tracking_repository.dart';
|
|
|
|
|
import 'package:flux/features/tracking/models/tracking_model.dart';
|
2026-05-07 16:28:01 +02:00
|
|
|
import 'package:get_it/get_it.dart';
|
|
|
|
|
import 'ticket_list_state.dart';
|
|
|
|
|
|
|
|
|
|
class TicketListCubit extends Cubit<TicketListState> {
|
|
|
|
|
final TicketRepository _repository = GetIt.I.get<TicketRepository>();
|
2026-05-24 12:42:11 +02:00
|
|
|
final TrackingRepository _trackingRepository = GetIt.I
|
|
|
|
|
.get<TrackingRepository>();
|
2026-05-07 16:28:01 +02:00
|
|
|
static const int _limit = 20; // Paginazione a blocchi di 20
|
|
|
|
|
|
|
|
|
|
TicketListCubit() : super(const TicketListState()) {
|
2026-05-15 13:32:34 +02:00
|
|
|
loadTickets(refresh: true);
|
2026-05-07 16:28:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Recupera i ticket. Se reset = true, svuota la lista e riparte da offset 0.
|
2026-05-15 13:32:34 +02:00
|
|
|
Future<void> loadTickets({bool refresh = false}) async {
|
2026-05-07 16:28:01 +02:00
|
|
|
if (state.isLoading) return;
|
2026-05-15 13:32:34 +02:00
|
|
|
if (!refresh && state.hasReachedMax) return;
|
2026-05-07 16:28:01 +02:00
|
|
|
|
|
|
|
|
emit(
|
|
|
|
|
state.copyWith(
|
|
|
|
|
isLoading: true,
|
|
|
|
|
errorMessage: '',
|
2026-05-15 13:32:34 +02:00
|
|
|
tickets: refresh ? [] : state.tickets,
|
2026-05-07 16:28:01 +02:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-15 13:32:34 +02:00
|
|
|
final currentOffset = refresh ? 0 : state.tickets.length;
|
2026-05-07 16:28:01 +02:00
|
|
|
|
|
|
|
|
final newTickets = await _repository.fetchStoreTickets(
|
|
|
|
|
offset: currentOffset,
|
|
|
|
|
limit: _limit,
|
|
|
|
|
searchTerm: state.searchTerm,
|
|
|
|
|
dateRange: state.dateRange,
|
|
|
|
|
statusFilter: state.statusFilter,
|
|
|
|
|
ticketTypeFilter: state.ticketTypeFilter,
|
|
|
|
|
staffIdFilter: state.staffIdFilter,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
emit(
|
|
|
|
|
state.copyWith(
|
2026-05-15 13:32:34 +02:00
|
|
|
tickets: refresh ? newTickets : [...state.tickets, ...newTickets],
|
2026-05-07 16:28:01 +02:00
|
|
|
isLoading: false,
|
|
|
|
|
hasReachedMax: newTickets.length < _limit,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Aggiorna i filtri e ricarica tutto da zero
|
|
|
|
|
void updateFilters({
|
|
|
|
|
String? searchTerm,
|
|
|
|
|
DateTimeRange? dateRange,
|
|
|
|
|
TicketStatus? statusFilter,
|
|
|
|
|
TicketType? ticketTypeFilter,
|
|
|
|
|
String? staffIdFilter,
|
|
|
|
|
bool clearSearch = false,
|
|
|
|
|
bool clearDate = false,
|
|
|
|
|
bool clearStatus = false,
|
|
|
|
|
}) {
|
|
|
|
|
emit(
|
|
|
|
|
state.copyWith(
|
|
|
|
|
searchTerm: searchTerm,
|
|
|
|
|
dateRange: dateRange,
|
|
|
|
|
statusFilter: statusFilter,
|
|
|
|
|
ticketTypeFilter: ticketTypeFilter,
|
|
|
|
|
staffIdFilter: staffIdFilter,
|
|
|
|
|
clearSearch: clearSearch,
|
|
|
|
|
clearDate: clearDate,
|
|
|
|
|
clearStatus: clearStatus,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-05-15 13:32:34 +02:00
|
|
|
loadTickets(refresh: true); // Applica i filtri e ricarica
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 11:51:26 +02:00
|
|
|
void toggleTicketSelection(TicketModel ticket) {
|
|
|
|
|
final currentSelection = Set<TicketModel>.from(state.selectedTickets);
|
|
|
|
|
if (currentSelection.contains(ticket)) {
|
|
|
|
|
currentSelection.remove(ticket);
|
2026-05-15 13:32:34 +02:00
|
|
|
} else {
|
2026-05-16 11:51:26 +02:00
|
|
|
currentSelection.add(ticket);
|
2026-05-15 13:32:34 +02:00
|
|
|
}
|
2026-05-16 11:51:26 +02:00
|
|
|
emit(state.copyWith(selectedTickets: currentSelection));
|
2026-05-15 13:32:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void clearSelection() {
|
2026-05-16 11:51:26 +02:00
|
|
|
emit(state.copyWith(selectedTickets: {}));
|
2026-05-15 13:32:34 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-16 11:51:26 +02:00
|
|
|
void selectAll(List<TicketModel> tickets) {
|
|
|
|
|
emit(state.copyWith(selectedTickets: tickets.toSet()));
|
2026-05-07 16:28:01 +02:00
|
|
|
}
|
2026-05-24 12:42:11 +02:00
|
|
|
|
|
|
|
|
Future<void> closeTicketsBulk({
|
|
|
|
|
required List<String> ticketIds,
|
|
|
|
|
Map<String, bool>? loanReturns,
|
|
|
|
|
}) async {
|
|
|
|
|
// 1. Escludiamo i ticket per cui NON è stato restituito il muletto
|
|
|
|
|
if (loanReturns != null) {
|
|
|
|
|
for (final map in loanReturns.entries) {
|
|
|
|
|
if (!map.value) {
|
|
|
|
|
ticketIds.remove(map.key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Se non c'è più nulla da chiudere (es. ha rifiutato tutto), usciamo
|
|
|
|
|
if (ticketIds.isEmpty) {
|
|
|
|
|
clearSelection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Prepariamo i ticket per il DB
|
|
|
|
|
final List<TicketModel> ticketsToUpdate = [];
|
|
|
|
|
for (final ticketId in ticketIds) {
|
|
|
|
|
final ticket = state.tickets
|
|
|
|
|
.firstWhere((ticket) => ticket.id == ticketId)
|
|
|
|
|
.copyWith(ticketStatus: TicketStatus.closed);
|
|
|
|
|
ticketsToUpdate.add(ticket);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Salviamo su DB (in background)
|
|
|
|
|
for (final ticket in ticketsToUpdate) {
|
|
|
|
|
await _repository.updateTicket(ticket);
|
|
|
|
|
await _trackingRepository.logQuickEvent(
|
|
|
|
|
companyId: ticket.companyId,
|
|
|
|
|
message: 'Ticket chiuso - Riconsegnato',
|
|
|
|
|
type: TrackingType.statusChange,
|
|
|
|
|
parentId: ticket.id!,
|
|
|
|
|
parentType: TrackingParentType.ticket,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. LA MAGIA: AGGIORNAMENTO LOCALE ISTANTANEO
|
|
|
|
|
final updatedTickets = state.tickets.map((t) {
|
|
|
|
|
if (ticketIds.contains(t.id)) {
|
|
|
|
|
return t.copyWith(ticketStatus: TicketStatus.closed);
|
|
|
|
|
}
|
|
|
|
|
return t;
|
|
|
|
|
}).toList();
|
|
|
|
|
|
|
|
|
|
// 5. Emettiamo il nuovo stato aggiornato e puliamo la selezione in un colpo solo
|
|
|
|
|
emit(
|
|
|
|
|
state.copyWith(
|
|
|
|
|
tickets: updatedTickets,
|
|
|
|
|
selectedTickets: {}, // Equivalente di clearSelection()
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Opzionale: Se vuoi comunque riallinearti al server in modo silenzioso dopo l'animazione
|
|
|
|
|
// loadTickets(refresh: true);
|
|
|
|
|
}
|
2026-06-02 11:52:31 +02:00
|
|
|
|
|
|
|
|
Future<void> deleteTickets(List<TicketModel> 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()));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-07 16:28:01 +02:00
|
|
|
}
|