ticket refinements
This commit is contained in:
@@ -47,7 +47,7 @@ class DashboardNoteListCubit extends Cubit<DashboardNoteListState> {
|
|||||||
Future<void> _loadNotesSilently() async {
|
Future<void> _loadNotesSilently() async {
|
||||||
try {
|
try {
|
||||||
final notes = await _repository.getNotes();
|
final notes = await _repository.getNotes();
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardNoteListStatus.success,
|
status: DashboardNoteListStatus.success,
|
||||||
@@ -56,6 +56,7 @@ class DashboardNoteListCubit extends Cubit<DashboardNoteListState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardNoteListStatus.failure,
|
status: DashboardNoteListStatus.failure,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class DashboardStoreOperationListCubit
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
);
|
);
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardStoreOperationListStatus.success,
|
status: DashboardStoreOperationListStatus.success,
|
||||||
@@ -65,6 +66,7 @@ class DashboardStoreOperationListCubit
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardStoreOperationListStatus.failure,
|
status: DashboardStoreOperationListStatus.failure,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class DashboardStoreTicketListCubit
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
);
|
);
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardStoreTicketListStatus.success,
|
status: DashboardStoreTicketListStatus.success,
|
||||||
@@ -68,6 +68,7 @@ class DashboardStoreTicketListCubit
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardStoreTicketListStatus.failure,
|
status: DashboardStoreTicketListStatus.failure,
|
||||||
@@ -76,4 +77,10 @@ class DashboardStoreTicketListCubit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
stopListening();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class DashboardTaskListCubit extends Cubit<DashboardTaskListState> {
|
|||||||
statuses: [TaskStatus.open, TaskStatus.inProgress],
|
statuses: [TaskStatus.open, TaskStatus.inProgress],
|
||||||
limit: 10,
|
limit: 10,
|
||||||
);
|
);
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardTaskListStatus.success,
|
status: DashboardTaskListStatus.success,
|
||||||
@@ -62,6 +62,7 @@ class DashboardTaskListCubit extends Cubit<DashboardTaskListState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isClosed) return;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: DashboardTaskListStatus.failure,
|
status: DashboardTaskListStatus.failure,
|
||||||
|
|||||||
@@ -153,10 +153,10 @@ class StaffRepository {
|
|||||||
|
|
||||||
// Assegna un membro a un negozio
|
// Assegna un membro a un negozio
|
||||||
Future<void> assignStaffToStore(String staffId, String storeId) async {
|
Future<void> assignStaffToStore(String staffId, String storeId) async {
|
||||||
await _supabase.from(Tables.staffInStores).insert({
|
await _supabase.from(Tables.staffInStores).upsert({
|
||||||
'staff_member_id': staffId,
|
'staff_member_id': staffId,
|
||||||
'store_id': storeId,
|
'store_id': storeId,
|
||||||
});
|
}, onConflict: 'staff_member_id,store_id'); // Evita duplicati
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rimuove l'assegnazione
|
// Rimuove l'assegnazione
|
||||||
|
|||||||
@@ -367,4 +367,22 @@ class TicketFormCubit extends Cubit<TicketFormState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> 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',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,16 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||||
// Adatta gli import al tuo progetto!
|
// 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 {
|
class TicketFormState extends Equatable {
|
||||||
final TicketModel ticket;
|
final TicketModel ticket;
|
||||||
|
|||||||
@@ -158,4 +158,19 @@ class TicketListCubit extends Cubit<TicketListState> {
|
|||||||
// Opzionale: Se vuoi comunque riallinearti al server in modo silenzioso dopo l'animazione
|
// Opzionale: Se vuoi comunque riallinearti al server in modo silenzioso dopo l'animazione
|
||||||
// loadTickets(refresh: true);
|
// loadTickets(refresh: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,6 +348,32 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
|||||||
trackingCubit.loadTrackings(ticketId, TrackingParentType.ticket);
|
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<TicketFormCubit>().deleteTicket();
|
||||||
|
Navigator.of(context).pop(); // Chiude il dialog
|
||||||
|
},
|
||||||
|
child: const Text('Cancella Ticket'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -359,6 +385,10 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
|||||||
_syncTextControllers(state.ticket);
|
_syncTextControllers(state.ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.status == TicketFormStatus.deleted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
if (state.status == TicketFormStatus.success) {
|
if (state.status == TicketFormStatus.success) {
|
||||||
context.read<TicketListCubit>().loadTickets(refresh: true);
|
context.read<TicketListCubit>().loadTickets(refresh: true);
|
||||||
_showSuccessActions(
|
_showSuccessActions(
|
||||||
@@ -388,66 +418,61 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
|||||||
: 'Modifica Ticket - Operatore: ${state.ticket.createdByName}',
|
: 'Modifica Ticket - Operatore: ${state.ticket.createdByName}',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
BlocBuilder<TicketFormCubit, TicketFormState>(
|
if (ticket.id != null) ...[
|
||||||
builder: (context, state) {
|
Padding(
|
||||||
final ticket = state.ticket;
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
// Se il ticket non è ancora salvato, niente azioni rapide
|
vertical: 8.0,
|
||||||
if (ticket.id == null || ticket.id!.isEmpty) {
|
),
|
||||||
return const SizedBox.shrink();
|
child: FilledButton.icon(
|
||||||
}
|
onPressed: () => _deleteTicket(ticket, color: Colors.red),
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
// CONDIZIONE A: Da iniziare
|
label: const Text('Cancella Ticket'),
|
||||||
if (ticket.ticketStatus == TicketStatus.open ||
|
),
|
||||||
ticket.ticketStatus == TicketStatus.waitingForParts) {
|
),
|
||||||
return Padding(
|
],
|
||||||
padding: const EdgeInsets.symmetric(
|
if (ticket.ticketStatus == TicketStatus.open ||
|
||||||
horizontal: 16.0,
|
ticket.ticketStatus == TicketStatus.waitingForParts) ...[
|
||||||
vertical: 8.0,
|
// CONDIZIONE A: Da iniziare
|
||||||
),
|
Padding(
|
||||||
child: FilledButton.icon(
|
padding: const EdgeInsets.symmetric(
|
||||||
style: FilledButton.styleFrom(
|
horizontal: 16.0,
|
||||||
backgroundColor:
|
vertical: 8.0,
|
||||||
Colors.amber.shade700, // Colore Action
|
),
|
||||||
),
|
child: FilledButton.icon(
|
||||||
onPressed: () async {
|
style: FilledButton.styleFrom(
|
||||||
StaffMemberModel? takenBy = await getStaffMember(
|
backgroundColor: Colors.amber.shade700, // Colore Action
|
||||||
context,
|
),
|
||||||
);
|
onPressed: () async {
|
||||||
if (takenBy == null || !context.mounted) return;
|
StaffMemberModel? takenBy = await getStaffMember(context);
|
||||||
context.read<TicketFormCubit>().takeInCharge(
|
if (takenBy == null || !context.mounted) return;
|
||||||
staffId: takenBy.id!,
|
context.read<TicketFormCubit>().takeInCharge(
|
||||||
staffName: takenBy.name,
|
staffId: takenBy.id!,
|
||||||
);
|
staffName: takenBy.name,
|
||||||
_navigateToWorkspace(ticket.id!);
|
);
|
||||||
},
|
_navigateToWorkspace(ticket.id!);
|
||||||
icon: const Icon(Icons.play_arrow, color: Colors.white),
|
},
|
||||||
label: const Text(
|
icon: const Icon(Icons.play_arrow, color: Colors.white),
|
||||||
'Prendi in Carico',
|
label: const Text(
|
||||||
style: TextStyle(color: Colors.white),
|
'Prendi in Carico',
|
||||||
),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
// CONDIZIONE B: Già in lavorazione
|
],
|
||||||
else if (ticket.ticketStatus == TicketStatus.inProgress) {
|
if (ticket.ticketStatus == TicketStatus.inProgress) ...[
|
||||||
return Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
vertical: 8.0,
|
vertical: 8.0,
|
||||||
),
|
),
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: () => _navigateToWorkspace(ticket.id!),
|
onPressed: () => _navigateToWorkspace(ticket.id!),
|
||||||
icon: const Icon(Icons.handyman),
|
icon: const Icon(Icons.handyman),
|
||||||
label: const Text('Vai a Lavorazione'),
|
label: const Text('Vai a Lavorazione'),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
],
|
||||||
|
|
||||||
// Se è chiuso o in altri stati strani, nascondiamo il bottone
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
padding: const EdgeInsets.only(right: 16.0),
|
||||||
child: Chip(
|
child: Chip(
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ class TicketList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _deleteTickets(BuildContext context) {
|
||||||
|
context.read<TicketListCubit>().deleteTickets(
|
||||||
|
state.selectedTickets.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
@@ -178,6 +184,11 @@ class TicketList extends StatelessWidget {
|
|||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
alignment: WrapAlignment.end,
|
alignment: WrapAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
IconButton.filled(
|
||||||
|
tooltip: 'Elimina',
|
||||||
|
onPressed: () => _deleteTickets(context),
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
),
|
||||||
IconButton.filled(
|
IconButton.filled(
|
||||||
tooltip: 'Riconsegna',
|
tooltip: 'Riconsegna',
|
||||||
onPressed: () => _setStatusClosed(context),
|
onPressed: () => _setStatusClosed(context),
|
||||||
|
|||||||
Reference in New Issue
Block a user