import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/widgets/shared_forms/shared_files_section.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/operations/blocs/operation_form_cubit.dart'; import 'package:flux/features/operations/models/operation_model.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/attachments_section.dart'; import 'package:flux/core/widgets/shared_forms/staff_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(); final _referenceController = TextEditingController(); final _noteController = TextEditingController(); final _freeTextSubtypeController = TextEditingController(); final _freeTextDescriptionController = TextEditingController(); final List _availableTypes = [ 'AL', 'MNP', 'NIP', 'UNICA', 'TELEPASS', 'Energy', 'Fin', 'Entertainment', 'Custom', ]; bool _isInitialized = false; @override void initState() { super.initState(); context.read().initForm( existingOperation: widget.existingOperation, operationId: widget.operationId, ); } @override void dispose() { _referenceController.dispose(); _noteController.dispose(); _freeTextSubtypeController.dispose(); _freeTextDescriptionController.dispose(); super.dispose(); } void _syncTextControllers(OperationModel model) { if (_referenceController.text.isEmpty) { _referenceController.text = model.reference; } if (_noteController.text.isEmpty) { _noteController.text = model.note; } if (_freeTextSubtypeController.text.isEmpty) { _freeTextSubtypeController.text = model.subtype ?? ''; } if (_freeTextDescriptionController.text.isEmpty) { _freeTextDescriptionController.text = model.description ?? ''; } // Se è una nuova pratica (draft), impostiamo di default il target su OK per comodità UI if (model.id == null && model.status == OperationStatus.draft) { // Usiamo addPostFrameCallback per non interferire con il build attuale WidgetsBinding.instance.addPostFrameCallback((_) { // Supponendo tu aggiunga la possibilità di aggiornare lo status nel metodo updateFields del Cubit // context.read().updateFields(status: OperationStatus.ok); }); } _isInitialized = true; } void _flushControllersToCubit() { context.read().updateFields( reference: _referenceController.text, note: _noteController.text, subtype: _freeTextSubtypeController.text, description: _freeTextDescriptionController.text, ); } void _saveOperation({ required OperationStatus targetStatus, required bool keepAdding, }) { if (_formKey.currentState!.validate()) { _flushControllersToCubit(); context.read().saveOperation( targetStatus: targetStatus, keepAdding: keepAdding, ); } } Future _generateIdForQr() async { if (!_formKey.currentState!.validate()) return null; _flushControllersToCubit(); final attachmentsBloc = context.read(); // Presumo tu abbia creato il metodo saveOperationDraft() nel Cubit! final newId = await context.read().saveOperationDraft(); if (newId != null && context.mounted) { attachmentsBloc.add(ParentEntitySavedEvent(newId)); } return newId; } // Helper per assegnare un colore agli stati Color _getStatusColor(OperationStatus status) { switch (status) { case OperationStatus.success: return Colors.green; case OperationStatus.waitingForAction: return Colors.orange; case OperationStatus.waitingForSupport: return Colors.blue; case OperationStatus.failure: return Colors.red; case OperationStatus.draft: return Colors.grey; } } @override Widget build(BuildContext context) { final theme = Theme.of(context); return BlocConsumer( listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.status == OperationFormStatus.ready && !_isInitialized) { _syncTextControllers(state.operation); } if (state.status == OperationFormStatus.success) { Navigator.of(context).pop(); } else if (state.status == OperationFormStatus.successAndAddAnother) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Operazione salvata! Inserisci la prossima'), ), ); _freeTextSubtypeController.clear(); _freeTextDescriptionController.clear(); } else if (state.status == OperationFormStatus.failure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.errorMessage ?? 'Errore di salvataggio'), backgroundColor: Colors.red, ), ); } }, builder: (context, state) { if (!_isInitialized && (widget.operationId != null || widget.existingOperation != null) && state.status == OperationFormStatus.loading) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } // Determiniamo lo stato da mostrare nel form. // Se è una bozza appena creata, mostriamo visivamente "OK" come default per il salvataggio. final displayStatus = state.operation.status == OperationStatus.draft && state.operation.id == null ? OperationStatus.success : state.operation.status; return Scaffold( appBar: AppBar( title: Text( state.operation.id == null ? 'Nuova Pratica' : 'Modifica Pratica', ), // Mettiamo un piccolo indicatore visivo anche nella AppBar se non è OK actions: displayStatus != OperationStatus.success && displayStatus != OperationStatus.draft ? [ Padding( padding: const EdgeInsets.only(right: 16.0), child: Chip( label: Text( displayStatus.displayName, style: const TextStyle( color: Colors.white, fontSize: 12, ), ), backgroundColor: _getStatusColor(displayStatus), ), ), ] : null, ), body: Form( key: _formKey, child: LayoutBuilder( builder: (context, constraints) { final isUltraWide = constraints.maxWidth > 1400; final isDesktop = constraints.maxWidth > 900; if (isUltraWide) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 4, child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: _buildMainFormContent( theme, state, displayStatus, showFiles: false, ), ), ), VerticalDivider(width: 1, color: theme.dividerColor), Expanded( flex: 3, child: Padding( padding: const EdgeInsets.all(16.0), child: _buildNotesSection(isDesktop: true), ), ), VerticalDivider(width: 1, color: theme.dividerColor), Expanded( flex: 3, child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: SharedFilesSection( titleNameForUpload: state.operation.customerDisplayName ?? 'Nuova operazione', onGenerateIdForQr: _generateIdForQr, ), ), ), ], ); } else if (isDesktop) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 7, child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: _buildMainFormContent( theme, state, displayStatus, ), ), ), VerticalDivider(width: 1, color: theme.dividerColor), Expanded( flex: 3, child: Padding( padding: const EdgeInsets.all(16.0), child: _buildNotesSection(isDesktop: true), ), ), ], ); } else { return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildMainFormContent(theme, state, displayStatus), const Divider(height: 32), _buildNotesSection(isDesktop: false), const SizedBox(height: 80), ], ), ); } }, ), ), bottomNavigationBar: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Expanded( flex: 1, child: OutlinedButton( onPressed: state.status == OperationFormStatus.saving ? null : () => _saveOperation( keepAdding: true, targetStatus: displayStatus, // <-- Usiamo lo stato selezionato nel form! ), child: const Text( 'Salva e Aggiungi Altro', textAlign: TextAlign.center, ), ), ), const SizedBox(width: 12), Expanded( flex: 1, child: ElevatedButton( style: ElevatedButton.styleFrom( // Se c'è un KO o un blocco, cambiamo il colore del bottone principale per attirare l'attenzione backgroundColor: displayStatus != OperationStatus.success && displayStatus != OperationStatus.draft ? _getStatusColor(displayStatus) : null, foregroundColor: displayStatus != OperationStatus.success && displayStatus != OperationStatus.draft ? Colors.white : null, ), onPressed: state.status == OperationFormStatus.saving ? null : () => _saveOperation( keepAdding: false, targetStatus: displayStatus, // <-- Usiamo lo stato selezionato nel form! ), child: state.status == OperationFormStatus.saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Text('Salva ed Esci'), ), ), ], ), ), ), ); }, ); } Widget _buildMainFormContent( ThemeData theme, OperationFormState state, OperationStatus displayStatus, { bool showFiles = true, }) { final currentOp = state.operation; final currentType = currentOp.type; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ StaffSection( staffId: currentOp.staffId, staffName: currentOp.staffDisplayName, onStaffSelected: (staff) => { context.read().updateFields( staffId: staff.id, staffDisplayName: staff.name, ), }, ), const Divider(height: 50), // --- SEZIONE STATO OPERAZIONE --- _buildSectionTitle('Esito / Stato Operazione'), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: _getStatusColor(displayStatus).withValues(alpha: 0.1), border: Border.all( color: _getStatusColor(displayStatus).withValues(alpha: 0.3), ), borderRadius: BorderRadius.circular(12), ), child: DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, value: displayStatus, icon: Icon( Icons.arrow_drop_down, color: _getStatusColor(displayStatus), ), items: OperationStatus.values .where( (s) => s != OperationStatus.draft, ) // Nascondiamo 'Bozza' dal menu .map( (status) => DropdownMenuItem( value: status, child: Row( children: [ Icon( status == OperationStatus.success ? Icons.check_circle : Icons.error_outline, color: _getStatusColor(status), size: 20, ), const SizedBox(width: 12), Text( status.displayName, style: TextStyle( fontWeight: FontWeight.w600, color: _getStatusColor(status), ), ), ], ), ), ) .toList(), onChanged: (newStatus) { if (newStatus != null) { // Assicurati che il metodo updateFields nel tuo Cubit accetti anche 'status' context.read().updateFields( status: newStatus, ); } }, ), ), ), const SizedBox(height: 8), Text( displayStatus == OperationStatus.success ? 'Lascia OK se la pratica è stata caricata con successo.' : 'Attenzione: la pratica verrà salvata come ${displayStatus.displayName}.', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), const Divider(height: 32), _buildSectionTitle('Cliente & Riferimento'), SharedCustomerSection( customerId: currentOp.customerId, customerName: currentOp.customerDisplayName, onCustomerSelected: (customer) { context.read().updateFields( customerId: customer.id, customerDisplayName: customer.name, ); }, ), 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), _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) { context.read().setTypeWithSmartDefault( type, ); } }, ); }).toList(), ), const Divider(height: 32), _buildSectionTitle('Dettagli Servizio'), DetailsSection( currentOp: currentOp, currentType: currentType, freeTextSubtypeController: _freeTextSubtypeController, freeTextDescriptionController: _freeTextDescriptionController, durationQuickPicks: _buildDurationQuickPicks(currentOp), ), // QUANTITÀ Row( children: [ const Text('Quantità: '), IconButton( icon: const Icon(Icons.remove), onPressed: () { final q = currentOp.quantity; if (q > 1) { context.read().updateFields( quantity: q - 1, ); } }, ), Text( '${currentOp.quantity}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.add), onPressed: () { final q = currentOp.quantity; context.read().updateFields( quantity: q + 1, ); }, ), ], ), const Divider(height: 32), if (showFiles) ...[ /* SharedAttachmentsSection( parentType: AttachmentParentType.operation, parentId: currentOp.id, ), */ SharedFilesSection( titleNameForUpload: state.operation.customerDisplayName ?? 'Nuova pratica', onGenerateIdForQr: _generateIdForQr, ), ], ], ); } 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(); context.read().updateFields( expirationDate: DateTime( now.year, now.month + months, now.day, ), ); }, ), ); }).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(), ), ); return isDesktop ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title, const SizedBox(height: 8), Expanded(child: noteField), ], ) : 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), ), ); } }