import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/customers/blocs/customers_cubit.dart'; import 'package:flux/features/customers/ui/quick_customer_dialog.dart'; import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; import 'package:flux/features/master_data/products/ui/quick_product_dialog.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'; // import 'package:flux/features/attachments/ui/operation_files_section.dart'; class OperationFormScreen extends StatefulWidget { final String? operationId; final OperationModel? existingOperation; const OperationFormScreen({ super.key, this.operationId, this.existingOperation, }); @override State createState() => _OperationFormScreenState(); } class _OperationFormScreenState extends State { final _formKey = GlobalKey(); // TEXT CONTROLLERS (Unici detentori di stato locale per evitare lag) final _referenceController = TextEditingController(); final _noteController = TextEditingController(); final _freeTextSubtypeController = TextEditingController(); final List _availableTypes = [ 'AL', 'MNP', 'NIP', 'UNICA', 'TELEPASS', 'Energy', 'Fin', 'Entertainment', 'Custom', ]; bool _doesProviderMatchOperationType(dynamic provider, String operationType) { // Se è Custom o non riconosciuto, mostriamo tutto if (operationType == 'Custom') return true; // Qui mappiamo il tipo di operazione scelto con i bool del ProviderModel switch (operationType) { case 'AL': return provider.telefoniaMobile == true; case 'MNP': return provider.telefoniaMobile == true; case 'NIP': return provider.telefoniaFissa == true; case 'UNICA': return provider.telefoniaFissa == true || provider.telefoniaMobile == true; case 'Energy': return provider.energia == true; case 'Fin': return provider.finanziamenti == true; case 'Entertainment': return provider.intrattenimento == true; case 'TELEPASS': return provider.telepass == true; default: return true; } } bool _isInitialized = false; @override void initState() { super.initState(); // Inizializziamo il form nel Cubit context.read().initOperationForm( existingOperation: widget.existingOperation, operationId: widget.operationId, ); } @override void dispose() { _referenceController.dispose(); _noteController.dispose(); _freeTextSubtypeController.dispose(); super.dispose(); } // Sincronizza SOLO i testi liberi quando il Cubit ha caricato da DB void _syncTextControllers(OperationModel model) { if (_referenceController.text.isEmpty && model.reference.isNotEmpty) { _referenceController.text = model.reference; } if (_noteController.text.isEmpty && model.note.isNotEmpty) { _noteController.text = model.note; } if (_freeTextSubtypeController.text.isEmpty && model.subtype != null && model.subtype!.isNotEmpty) { _freeTextSubtypeController.text = model.subtype!; } _isInitialized = true; } // --- LOGICA DI SALVATAGGIO --- void _saveOperation({required bool keepAdding}) { if (_formKey.currentState!.validate()) { final cubit = context.read(); final currentOperation = cubit.state.currentOperation!; // 1. "Travasiamo" i testi liberi dai controller al Modello prima di salvare final operationToSave = currentOperation.copyWith( reference: _referenceController.text, note: _noteController.text, // subtype: currentOperation.type == 'Custom' ? _customSubtypeController.text : currentOperation.subtype, // <-- Scommenta quando aggiungi subtype ); // 2. Aggiorniamo il Cubit con i testi cubit.initOperationForm(existingOperation: operationToSave); // 3. Salviamo! cubit.saveCurrentOperation( targetStatus: OperationStatus.ok, shouldPop: !keepAdding, ); } } // --- MODALE SELEZIONE CLIENTE --- void _showCustomerModal() { String currentSearchQuery = ''; showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (modalContext) { return DraggableScrollableSheet( initialChildSize: 0.8, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (_, scrollController) { return Column( children: [ // Header Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Seleziona Cliente', style: Theme.of(context).textTheme.titleLarge, ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(modalContext), ), ], ), ), // Barra di Ricerca Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextField( autofocus: true, decoration: InputDecoration( hintText: 'Cerca per nome, telefono o email...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), onChanged: (query) { currentSearchQuery = query; context.read().searchCustomers(query); }, ), ), // Pulsante Nuovo Cliente Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( minimumSize: const Size.fromHeight(48), ), icon: const Icon(Icons.person_add), label: const Text('Crea Nuovo Cliente'), onPressed: () async { final OperationsCubit operationsCubit = context .read(); // APRIAMO LA DIALOG RAPIDA CON LA MAGIA DEL BLOC PROVIDER final newCustomer = await showDialog( context: context, builder: (dialogContext) { return BlocProvider.value( value: context.read(), child: QuickCustomerDialog( initialQuery: currentSearchQuery, // <-- Passiamo quello che ha digitato! ), ); }, ); // Se l'ha creato davvero (e non ha premuto annulla)... if (newCustomer != null) { // 1. Aggiorniamo il form delle operazioni operationsCubit.updateOperationFields( customerId: newCustomer.id, customerDisplayName: newCustomer.name, ); // 2. Chiudiamo la BottomSheet dei clienti per tornare alla form! if (context.mounted) { Navigator.pop(modalContext); } } }, ), ), const Divider(), // Lista Clienti dal Bloc Expanded( child: BlocBuilder( builder: (context, state) { if (state.status == CustomersStatus.loading) { return const Center(child: CircularProgressIndicator()); } if (state.customers.isEmpty) { return const Center( child: Text( 'Nessun cliente trovato.', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( controller: scrollController, itemCount: state.customers.length, itemBuilder: (context, index) { final customer = state.customers[index]; return ListTile( leading: CircleAvatar( child: Text( customer.name.substring(0, 1).toUpperCase(), ), ), title: Text( customer.name, style: const TextStyle( fontWeight: FontWeight.bold, ), ), subtitle: Text( '${customer.phoneNumber} • ${customer.email}', ), onTap: () { // Aggiorniamo il form tramite il Cubit delle operazioni context .read() .updateOperationFields( customerId: customer.id, // customer.id customerDisplayName: customer.name, // customer.name ); Navigator.pop(modalContext); }, ); }, ); }, ), ), ], ); }, ); }, ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return BlocConsumer( listenWhen: (previous, current) => previous.status != current.status || previous.currentOperation?.id != current.currentOperation?.id, listener: (context, state) { // Sincronizzazione iniziale if (state.status == OperationsStatus.ready && state.currentOperation != null && !_isInitialized) { _syncTextControllers(state.currentOperation!); } if (state.status == OperationsStatus.saved) { Navigator.of(context).pop(); } else if (state.status == OperationsStatus.savedNoPop) { context.read().prepareNextOperationInBatch(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Servizio aggiunto! Inserisci il prossimo.'), ), ); // Ripuliamo SOLO i testi liberi (il Cubit gestisce già i suoi reset) _referenceController.clear(); _noteController.clear(); _freeTextSubtypeController.clear(); } else if (state.status == OperationsStatus.failure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.errorMessage ?? 'Errore'), backgroundColor: theme.colorScheme.error, ), ); } }, builder: (context, state) { // Loader iniziale if (!_isInitialized && (widget.operationId != null || widget.existingOperation != null) && state.status == OperationsStatus.loading) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar( title: Text( state.currentOperation?.id == null ? 'Nuova Pratica' : 'Modifica Pratica', ), ), body: Form( key: _formKey, child: LayoutBuilder( builder: (context, constraints) { final isDesktop = constraints.maxWidth > 900; if (isDesktop) { // --- LAYOUT DESKTOP --- return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 7, child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: _buildMainFormContent(theme, state), ), ), VerticalDivider(width: 1, color: theme.dividerColor), Expanded( flex: 3, child: Padding( padding: const EdgeInsets.all(16.0), child: _buildNotesSection(isDesktop: true), ), ), ], ); } else { // --- LAYOUT MOBILE --- return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildMainFormContent(theme, state), const Divider(height: 32), _buildNotesSection(isDesktop: false), const SizedBox(height: 80), ], ), ); } }, ), ), // --- LA CASSA --- bottomNavigationBar: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Expanded( flex: 1, child: OutlinedButton( onPressed: state.status == OperationsStatus.saving ? null : () => _saveOperation(keepAdding: true), child: const Text( 'Salva e Aggiungi Altro', textAlign: TextAlign.center, ), ), ), const SizedBox(width: 12), Expanded( flex: 1, child: ElevatedButton( onPressed: state.status == OperationsStatus.saving ? null : () => _saveOperation(keepAdding: false), child: state.status == OperationsStatus.saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Text('Salva ed Esci'), ), ), ], ), ), ), ); }, ); } // --- COSTRUTTORI UI COMPONENTI --- Widget _buildMainFormContent(ThemeData theme, OperationsState state) { final currentOp = state.currentOperation; final currentType = currentOp?.type ?? 'AL'; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- BLOCCO 1: CONTESTO --- _buildSectionTitle('Cliente & Riferimento'), _buildCustomerSelector(currentOp), const SizedBox(height: 16), TextFormField( controller: _referenceController, decoration: const InputDecoration( labelText: 'Riferimento (es. numero di telefono, targa...)', prefixIcon: Icon(Icons.tag), ), ), const Divider(height: 32), // --- BLOCCO 2: TIPO DI OPERAZIONE --- _buildSectionTitle('Cosa stiamo facendo?'), Wrap( spacing: 8.0, runSpacing: 8.0, children: _availableTypes.map((type) { return ChoiceChip( label: Text(type), selected: currentType == type, onSelected: (selected) { if (selected) { // Diciamo al Cubit di cambiare tipo e spianare i campi dipendenti context.read().setTypeWithSmartDefault(type); } }, ); }).toList(), ), const Divider(height: 32), // --- BLOCCO 3: DETTAGLI REATTIVI --- _buildSectionTitle('Dettagli Servizio'), // PROVIDER (Mostrato quasi sempre) ListTile( title: const Text('Seleziona Gestore'), subtitle: Text( (currentOp?.providerDisplayName != null && currentOp!.providerDisplayName!.isNotEmpty) ? currentOp.providerDisplayName! : 'Nessun gestore selezionato', style: TextStyle( color: (currentOp?.providerId == null || currentOp!.providerId!.isEmpty) ? Colors.grey : null, fontWeight: (currentOp?.providerId == null || currentOp!.providerId!.isEmpty) ? FontWeight.normal : FontWeight.bold, ), ), trailing: const Icon(Icons.arrow_drop_down), shape: RoundedRectangleBorder( side: BorderSide(color: theme.dividerColor), borderRadius: BorderRadius.circular(8), ), onTap: () { _showProviderModal(currentType); }, ), const SizedBox(height: 16), // 1. SCENARIO ENERGY (Dropdown Fisso) if (currentType == 'Energy') ...[ DropdownButtonFormField( initialValue: (currentOp?.subtype != null && currentOp!.subtype!.isNotEmpty) ? currentOp.subtype : null, decoration: const InputDecoration(labelText: 'Dettaglio Fornitura'), items: [ 'Luce', 'Gas', 'Dual', ].map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(), onChanged: (val) { if (val != null) { context.read().updateOperationFields( subtype: val, ); } }, ), const SizedBox(height: 16), ], // 2. SCENARIO FIN (Ricerca Modello/Prodotto) if (currentType == 'Fin') ...[ ListTile( title: const Text('Seleziona Dispositivo/Prodotto'), subtitle: Text( (currentOp?.modelDisplayName != null && currentOp!.modelDisplayName!.isNotEmpty) ? currentOp.modelDisplayName! : 'Nessun modello selezionato', style: TextStyle( color: (currentOp?.modelId == null || currentOp!.modelId!.isEmpty) ? Colors.grey : null, fontWeight: (currentOp?.modelId == null || currentOp!.modelId!.isEmpty) ? FontWeight.normal : FontWeight.bold, ), ), trailing: const Icon(Icons.arrow_drop_down), shape: RoundedRectangleBorder( side: BorderSide(color: theme.dividerColor), borderRadius: BorderRadius.circular(8), ), onTap: _showModelModal, ), const SizedBox(height: 16), ], // 3. SCENARIO ENTERTAINMENT O CUSTOM (Testo libero) if (currentType == 'Entertainment' || currentType == 'Custom') ...[ TextFormField( controller: _freeTextSubtypeController, decoration: InputDecoration( labelText: currentType == 'Entertainment' ? 'Piattaforma (es. Netflix, DAZN, Spotify...)' : 'Specifica il servizio (es. Monopattino)', ), ), const SizedBox(height: 16), ], // SCADENZA (Reattivo per tipi complessi) if ([ 'Energy', 'Fin', 'Entertainment', 'Custom', ].contains(currentType)) ...[ const SizedBox(height: 8), // --- I CHIPS RAPIDI --- _buildDurationQuickPicks(currentOp), const SizedBox(height: 16), // --- IL SELETTORE MANUALE --- ListTile( title: const Text('Data di Scadenza Effettiva'), subtitle: Text( currentOp?.expirationDate != null ? "${currentOp!.expirationDate!.day}/${currentOp.expirationDate!.month}/${currentOp.expirationDate!.year}" : 'Nessuna scadenza impostata', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), trailing: const Icon(Icons.calendar_month, color: Colors.blue), tileColor: Colors.blue.withValues(alpha: 0.05), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide(color: Colors.blue, width: 0.5), ), onTap: () async { final OperationsCubit operationsCubit = context .read(); final date = await showDatePicker( context: context, initialDate: currentOp?.expirationDate ?? DateTime.now().add(const Duration(days: 365)), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 3650)), ); if (date != null) { operationsCubit.updateOperationFields(expirationDate: date); } }, ), const SizedBox(height: 16), ], // QUANTITÀ Row( children: [ const Text('Quantità: '), IconButton( icon: const Icon(Icons.remove), onPressed: () { final q = currentOp?.quantity ?? 1; if (q > 1) { context.read().updateOperationFields( quantity: q - 1, ); } }, ), Text( '${currentOp?.quantity ?? 1}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.add), onPressed: () { final q = currentOp?.quantity ?? 1; context.read().updateOperationFields( quantity: q + 1, ); }, ), ], ), const Divider(height: 32), // --- BLOCCO 5: ALLEGATI --- _buildSectionTitle('Documenti & Foto'), const Center( child: Text( "Widget File in arrivo...", style: TextStyle(color: Colors.grey), ), ), ], ); } Widget _buildDurationQuickPicks(OperationModel? currentOp) { final durations = [3, 6, 12, 24, 30, 36, 48]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Imposta durata rapida (mesi):", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.grey, ), ), const SizedBox(height: 8), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: durations.map((months) { return Padding( padding: const EdgeInsets.only(right: 8.0), child: ActionChip( label: Text("$months m"), backgroundColor: Colors.blue.withValues(alpha: 0.05), onPressed: () { final now = DateTime.now(); final newDate = DateTime( now.year, now.month + months, now.day, ); context.read().updateOperationFields( expirationDate: newDate, ); }, ), ); }).toList(), ), ), ], ); } Widget _buildNotesSection({required bool isDesktop}) { final title = _buildSectionTitle('Note Interne'); final noteField = TextFormField( controller: _noteController, keyboardType: TextInputType.multiline, minLines: isDesktop ? null : 5, maxLines: null, expands: isDesktop, textAlignVertical: TextAlignVertical.top, decoration: const InputDecoration( hintText: 'Incolla qui seriali, ICCID, IBAN, indirizzi...', alignLabelWithHint: true, border: OutlineInputBorder(), ), ); if (isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title, const SizedBox(height: 8), Expanded(child: noteField), ], ); } else { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [title, const SizedBox(height: 8), noteField], ); } } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Text( title, style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ); } Widget _buildCustomerSelector(OperationModel? currentOp) { final hasCustomer = currentOp?.customerId != null && currentOp!.customerId!.isNotEmpty; return InkWell( onTap: _showCustomerModal, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: Theme.of(context).colorScheme.primary), borderRadius: BorderRadius.circular(8), color: Theme.of( context, ).colorScheme.primaryContainer.withValues(alpha: 0.2), ), child: Row( children: [ const Icon(Icons.person), const SizedBox(width: 12), Expanded( child: Text( hasCustomer ? currentOp.customerDisplayName ?? '' : 'Seleziona Cliente *', style: TextStyle( fontWeight: hasCustomer ? FontWeight.bold : FontWeight.normal, color: hasCustomer ? null : Colors.grey, ), ), ), const Icon(Icons.search), ], ), ), ); } void _showProviderModal(String currentOperationType) { showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (modalContext) { return DraggableScrollableSheet( initialChildSize: 0.5, // Parte a metà schermo minChildSize: 0.4, maxChildSize: 0.8, expand: false, builder: (_, scrollController) { return Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Seleziona Gestore', style: Theme.of(context).textTheme.titleLarge, ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(modalContext), ), ], ), ), const Divider(), Expanded( child: BlocBuilder( // <--- Usa il tuo Cubit dei provider builder: (context, state) { if (state.isLoading) { return const Center(child: CircularProgressIndicator()); } // Simuliamo la lista di provider caricata dal tuo stato final allProviders = state.activeProviders; // Applichiamo il nostro filtro magico! final filteredProviders = allProviders .where( (p) => _doesProviderMatchOperationType( p, currentOperationType, ), ) .toList(); if (filteredProviders.isEmpty) { return const Center( child: Text( 'Nessun gestore compatibile con questo servizio.', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( controller: scrollController, itemCount: filteredProviders.length, itemBuilder: (context, index) { final provider = filteredProviders[index]; return ListTile( leading: const Icon(Icons.business), title: Text( provider.nome, style: const TextStyle( fontWeight: FontWeight.bold, ), ), onTap: () { // Selezione effettuata! Diciamo al Cubit delle operazioni di aggiornarsi context .read() .updateOperationFields( providerId: provider.id, providerDisplayName: provider .nome, // Fondamentale per la UI! ); Navigator.pop(modalContext); }, ); }, ); }, ), ), ], ); }, ); }, ); } // --- MODALE SELEZIONE MODELLO (PER FINANZIAMENTI) --- void _showModelModal() { showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (modalContext) { return DraggableScrollableSheet( initialChildSize: 0.6, minChildSize: 0.4, maxChildSize: 0.9, expand: false, builder: (_, scrollController) { return Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Seleziona Modello', style: Theme.of(context).textTheme.titleLarge, ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(modalContext), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextField( decoration: InputDecoration( hintText: 'Cerca modello (es. iPhone 15...)', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), onChanged: (query) { context.read().searchModels(query); }, ), ), Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( minimumSize: const Size.fromHeight(48), ), icon: const Icon(Icons.add), label: const Text('Aggiungi Modello al Volo'), onPressed: () async { final OperationsCubit operationsCubit = context .read(); // 1. Recuperiamo la lista dei brand (adatta questo in base a dove tieni i brand nel tuo stato) final existingBrands = context .read() .state .brands; // <-- Verifica che sia corretto! // 2. Apriamo il tuo Dialog. // ATTENZIONE DA CECCHINO: showDialog crea una nuova "rotta" sopra l'albero dei widget, // quindi dobbiamo passargli il Cubit usando BlocProvider.value per non farglielo perdere! final newModel = await showDialog( context: context, builder: (dialogContext) { return BlocProvider.value( value: context.read(), child: QuickProductDialog( existingBrands: existingBrands, ), ); }, ); // 3. Se l'utente ha effettivamente creato un modello e non ha premuto "Annulla"... if (newModel != null) { // A. Aggiorniamo il form del Cubit delle operazioni con il nuovo nato! operationsCubit.updateOperationFields( modelId: newModel.id, modelDisplayName: newModel .nameWithBrand, // <-- Verifica il nome della property ); // B. Chiudiamo ANCHE la BottomSheet dei modelli per far tornare l'utente al form principale if (context.mounted) { Navigator.pop(modalContext); } } }, ), ), const Divider(), Expanded( child: BlocBuilder( // <--- Usa il tuo Cubit dei modelli! builder: (context, state) { return ListView.builder( controller: scrollController, itemCount: state .models .length, // Sostituisci con state.models.length itemBuilder: (context, index) { final deviceModel = state.models[index]; return ListTile( leading: const Icon(Icons.devices), title: Text( deviceModel.nameWithBrand, style: const TextStyle( fontWeight: FontWeight.bold, ), ), onTap: () { context .read() .updateOperationFields( modelId: deviceModel.id, modelDisplayName: deviceModel.nameWithBrand, ); Navigator.pop(modalContext); }, ); }, ); }, ), ), ], ); }, ); }, ); } }