From c6321d6580c511334f08bfbc19f9ca17a44e6eb8 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Wed, 6 May 2026 20:40:02 +0200 Subject: [PATCH] =?UTF-8?q?ticket=20form=20funzionante!=20devo=20ancora=20?= =?UTF-8?q?provare=20a=20salvare=20per=C3=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/routes/app_router.dart | 31 ++++++++++++++++++- ..._section.dart => attachments_section.dart} | 0 ...mer_section.dart => customer_section.dart} | 0 ..._screen.dart => mobile_upload_screen.dart} | 0 ..._model_section.dart => model_section.dart} | 0 ..._staff_section.dart => staff_section.dart} | 31 ++++++++++++++----- lib/features/home/ui/home_screen.dart | 2 +- .../operations/ui/operation_form_screen.dart | 6 ++-- .../ui/widgets/details_section.dart | 2 +- .../tickets/blocs/ticket_form_cubit.dart | 26 +++++++++++++++- .../tickets/data/ticket_repository.dart | 2 +- .../tickets/ui/ticket_form_screen.dart | 20 +++++++----- 12 files changed, 97 insertions(+), 23 deletions(-) rename lib/core/widgets/shared_forms/{shared_attachments_section.dart => attachments_section.dart} (100%) rename lib/core/widgets/shared_forms/{shared_customer_section.dart => customer_section.dart} (100%) rename lib/core/widgets/shared_forms/{shared_mobile_upload_screen.dart => mobile_upload_screen.dart} (100%) rename lib/core/widgets/shared_forms/{shared_model_section.dart => model_section.dart} (100%) rename lib/core/widgets/shared_forms/{shared_staff_section.dart => staff_section.dart} (85%) diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index ff8160b..9673721 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -6,7 +6,7 @@ import 'package:flux/core/data/core_repository.dart'; import 'package:flux/core/layout/app_shell.dart'; import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/widgets/set_password_screen.dart'; -import 'package:flux/core/widgets/shared_forms/shared_mobile_upload_screen.dart'; +import 'package:flux/core/widgets/shared_forms/mobile_upload_screen.dart'; import 'package:flux/features/auth/ui/auth_screen.dart'; import 'package:flux/features/customers/blocs/customers_cubit.dart'; import 'package:flux/features/customers/models/customer_model.dart'; @@ -27,7 +27,10 @@ import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/operations/models/operation_model.dart'; import 'package:flux/features/operations/ui/operation_form_screen.dart'; import 'package:flux/features/operations/ui/operations_screen.dart'; +import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; +import 'package:flux/features/tickets/models/ticket_model.dart'; +import 'package:flux/features/tickets/ui/ticket_form_screen.dart'; import 'package:flux/features/tickets/ui/ticket_list_screen.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; @@ -157,6 +160,32 @@ class AppRouter { ), // --- DETTAGLI E OPERATIVITÀ (FUORI DALLA SHELL - TUTTO SCHERMO) --- + GoRoute( + // Il path sarà es. /tickets/form/123 oppure /tickets/form/new + path: '/tickets/form/:id', + builder: (context, state) { + // 1. Leggiamo l'ID dall'URL + final String pathId = state.pathParameters['id'] ?? 'new'; + + // 2. Leggiamo l'oggetto dalla RAM (se arriviamo da un tap interno all'app) + final TicketModel? ticketFromExtra = state.extra as TicketModel?; + + // 3. Capiamo se è un nuovo ticket o una modifica + final String? realTicketId = pathId == 'new' ? null : pathId; + context.read().loadStaffForStore( + GetIt.I.get().state.currentStore!.id!, + ); + context.read().loadCustomers(); + + return BlocProvider( + create: (context) => TicketFormCubit(), + child: TicketFormScreen( + ticketId: realTicketId, + existingTicket: ticketFromExtra, + ), + ); + }, + ), GoRoute( path: '/customer/:id', builder: (context, state) { diff --git a/lib/core/widgets/shared_forms/shared_attachments_section.dart b/lib/core/widgets/shared_forms/attachments_section.dart similarity index 100% rename from lib/core/widgets/shared_forms/shared_attachments_section.dart rename to lib/core/widgets/shared_forms/attachments_section.dart diff --git a/lib/core/widgets/shared_forms/shared_customer_section.dart b/lib/core/widgets/shared_forms/customer_section.dart similarity index 100% rename from lib/core/widgets/shared_forms/shared_customer_section.dart rename to lib/core/widgets/shared_forms/customer_section.dart diff --git a/lib/core/widgets/shared_forms/shared_mobile_upload_screen.dart b/lib/core/widgets/shared_forms/mobile_upload_screen.dart similarity index 100% rename from lib/core/widgets/shared_forms/shared_mobile_upload_screen.dart rename to lib/core/widgets/shared_forms/mobile_upload_screen.dart diff --git a/lib/core/widgets/shared_forms/shared_model_section.dart b/lib/core/widgets/shared_forms/model_section.dart similarity index 100% rename from lib/core/widgets/shared_forms/shared_model_section.dart rename to lib/core/widgets/shared_forms/model_section.dart diff --git a/lib/core/widgets/shared_forms/shared_staff_section.dart b/lib/core/widgets/shared_forms/staff_section.dart similarity index 85% rename from lib/core/widgets/shared_forms/shared_staff_section.dart rename to lib/core/widgets/shared_forms/staff_section.dart index 0aa8b86..ac52971 100644 --- a/lib/core/widgets/shared_forms/shared_staff_section.dart +++ b/lib/core/widgets/shared_forms/staff_section.dart @@ -22,6 +22,7 @@ class StaffSection extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); + // Se staffId è nullo, proviamo a preselezionare l'utente loggato final selectedStaffId = staffId ?? GetIt.I.get().state.currentStaffMember?.id; @@ -31,7 +32,8 @@ class StaffSection extends StatelessWidget { Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Text( - 'Operatore', + label ?? + 'Operatore', // <-- FIX: Ora usa l'etichetta passata dal form! style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), @@ -39,8 +41,28 @@ class StaffSection extends StatelessWidget { ), BlocBuilder( builder: (context, state) { - // Dati finti per farti vedere la UI, piallali quando attacchi il BlocBuilder! + // FIX: Aggiunto un controllo se sta caricando + if (state.status == StaffStatus.loading) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ); + } + final staffMembers = state.storeStaff; + + // FIX: Feedback visivo se la lista è vuota + if (staffMembers.isEmpty) { + return const Text( + 'Nessun operatore caricato. Controlla il Cubit!', + style: TextStyle(color: Colors.red), + ); + } + final currentLoggedStaffMember = GetIt.I .get() .state @@ -55,11 +77,6 @@ class StaffSection extends StatelessWidget { return GestureDetector( onTap: () { onStaffSelected(staff); - - /* context.read().updateOperationFields( - staffId: staff.id, - staffDisplayName: staff.name, - ); */ }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), diff --git a/lib/features/home/ui/home_screen.dart b/lib/features/home/ui/home_screen.dart index 9029872..98a4106 100644 --- a/lib/features/home/ui/home_screen.dart +++ b/lib/features/home/ui/home_screen.dart @@ -196,7 +196,7 @@ class HomeScreen extends StatelessWidget { color: Colors.redAccent, onTap: () { // Andiamo alla lista! (Da lì poi aggiungeremo il tasto "+" per il form) - context.push('/tickets'); + context.push('/tickets/form/new'); }, ), const SizedBox(width: 12), diff --git a/lib/features/operations/ui/operation_form_screen.dart b/lib/features/operations/ui/operation_form_screen.dart index cf58ad5..a92a719 100644 --- a/lib/features/operations/ui/operation_form_screen.dart +++ b/lib/features/operations/ui/operation_form_screen.dart @@ -4,10 +4,10 @@ import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/operations/blocs/operations_cubit.dart'; import 'package:flux/features/operations/models/operation_model.dart'; -import 'package:flux/core/widgets/shared_forms/shared_customer_section.dart'; +import 'package:flux/core/widgets/shared_forms/customer_section.dart'; import 'package:flux/features/operations/ui/widgets/details_section.dart'; -import 'package:flux/core/widgets/shared_forms/shared_attachments_section.dart'; -import 'package:flux/core/widgets/shared_forms/shared_staff_section.dart'; +import 'package:flux/core/widgets/shared_forms/attachments_section.dart'; +import 'package:flux/core/widgets/shared_forms/staff_section.dart'; import 'package:get_it/get_it.dart'; class OperationFormScreen extends StatefulWidget { diff --git a/lib/features/operations/ui/widgets/details_section.dart b/lib/features/operations/ui/widgets/details_section.dart index c2ab20c..73deeff 100644 --- a/lib/features/operations/ui/widgets/details_section.dart +++ b/lib/features/operations/ui/widgets/details_section.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/widgets/shared_forms/shared_model_section.dart'; +import 'package:flux/core/widgets/shared_forms/model_section.dart'; import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart'; import 'package:flux/features/operations/blocs/operations_cubit.dart'; import 'package:flux/features/operations/models/operation_model.dart'; diff --git a/lib/features/tickets/blocs/ticket_form_cubit.dart b/lib/features/tickets/blocs/ticket_form_cubit.dart index abe4479..8ee7280 100644 --- a/lib/features/tickets/blocs/ticket_form_cubit.dart +++ b/lib/features/tickets/blocs/ticket_form_cubit.dart @@ -17,12 +17,36 @@ class TicketFormCubit extends Cubit { ); /// 1. INIZIALIZZAZIONE (Se stiamo modificando un ticket esistente) - void initForm(TicketModel? existingTicket) { + Future initForm({String? id, TicketModel? existingTicket}) async { if (existingTicket != null) { + // SCENARIO 1 (App Native / Navigazione interna Web): + // Abbiamo l'oggetto intero passato via 'extra'. Lo mostriamo all'istante! emit( state.copyWith(ticket: existingTicket, status: TicketFormStatus.ready), ); + } else if (id != null) { + // SCENARIO 2 (Web Refresh o Link condiviso): + // L'utente ha premuto F5 su /tickets/form/123. L'extra è andato perso, ma abbiamo l'ID! + emit( + state.copyWith(status: TicketFormStatus.loading), + ); // Mostriamo uno spinner + try { + final fetchedTicket = await _repository.getTicketById( + id, + ); // Lo scarichiamo! + emit( + state.copyWith(ticket: fetchedTicket, status: TicketFormStatus.ready), + ); + } catch (e) { + emit( + state.copyWith( + status: TicketFormStatus.failure, + errorMessage: 'Ticket non trovato', + ), + ); + } } else { + // SCENARIO 3 (Nuovo Ticket): // È un nuovo ticket! Inseriamo i default base (Azienda, Negozio, Creatore) final currentUser = _sessionCubit.state.currentStaffMember; final currentStore = _sessionCubit.state.currentStore; diff --git a/lib/features/tickets/data/ticket_repository.dart b/lib/features/tickets/data/ticket_repository.dart index d274e22..ae01cb8 100644 --- a/lib/features/tickets/data/ticket_repository.dart +++ b/lib/features/tickets/data/ticket_repository.dart @@ -170,7 +170,7 @@ class TicketRepository { /// Recupera un ticket specifico CON TUTTE LE RELAZIONI espanse (Cliente e Modelli) /// Questa è la vera magia di Supabase! - Future getTicketWithDetails(String ticketId) async { + Future getTicketById(String ticketId) async { try { // Usiamo i nomi esatti delle Foreign Key che hai definito nell'SQL! final response = await _supabase diff --git a/lib/features/tickets/ui/ticket_form_screen.dart b/lib/features/tickets/ui/ticket_form_screen.dart index 42f2321..6eea21a 100644 --- a/lib/features/tickets/ui/ticket_form_screen.dart +++ b/lib/features/tickets/ui/ticket_form_screen.dart @@ -3,15 +3,16 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_form_state.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; -import 'package:flux/core/widgets/shared_forms/shared_customer_section.dart'; -import 'package:flux/core/widgets/shared_forms/shared_model_section.dart'; -import 'package:flux/core/widgets/shared_forms/shared_staff_section.dart'; +import 'package:flux/core/widgets/shared_forms/customer_section.dart'; +import 'package:flux/core/widgets/shared_forms/model_section.dart'; +import 'package:flux/core/widgets/shared_forms/staff_section.dart'; import 'package:flux/features/tickets/models/ticket_status_extension.dart'; // Il tuo widget agnostico dello staff class TicketFormScreen extends StatefulWidget { final TicketModel? existingTicket; + final String? ticketId; - const TicketFormScreen({super.key, this.existingTicket}); + const TicketFormScreen({super.key, this.existingTicket, this.ticketId}); @override State createState() => _TicketFormScreenState(); @@ -36,7 +37,10 @@ class _TicketFormScreenState extends State { void initState() { super.initState(); // Inizializziamo il Cubit - context.read().initForm(widget.existingTicket); + context.read().initForm( + id: widget.ticketId, + existingTicket: widget.existingTicket, + ); } @override @@ -150,7 +154,7 @@ class _TicketFormScreenState extends State { padding: const EdgeInsets.only(right: 16.0), child: Chip( label: Text( - ticket.ticketStatus!.name.toUpperCase(), + ticket.ticketStatus.name.toUpperCase(), style: const TextStyle(color: Colors.white, fontSize: 10), ), backgroundColor: ticket.ticketStatus.color, @@ -239,7 +243,7 @@ class _TicketFormScreenState extends State { children: [ Expanded( child: DropdownButtonFormField( - value: ticket.ticketType, + initialValue: ticket.ticketType, decoration: const InputDecoration( labelText: 'Tipo Lavorazione', ), @@ -261,7 +265,7 @@ class _TicketFormScreenState extends State { const SizedBox(width: 16), Expanded( child: DropdownButtonFormField( - value: ticket.ticketStatus, + initialValue: ticket.ticketStatus, decoration: const InputDecoration( labelText: 'Stato Attuale', ),