Files
flux/lib/features/operations/ui/operation_form_screen.dart

1046 lines
38 KiB
Dart
Raw Normal View History

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<OperationFormScreen> createState() => _OperationFormScreenState();
}
class _OperationFormScreenState extends State<OperationFormScreen> {
final _formKey = GlobalKey<FormState>();
// TEXT CONTROLLERS (Unici detentori di stato locale per evitare lag)
final _referenceController = TextEditingController();
final _noteController = TextEditingController();
final _freeTextSubtypeController = TextEditingController();
final List<String> _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<OperationsCubit>().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<OperationsCubit>();
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<CustomersCubit>().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<OperationsCubit>();
// APRIAMO LA DIALOG RAPIDA CON LA MAGIA DEL BLOC PROVIDER
final newCustomer = await showDialog(
context: context,
builder: (dialogContext) {
return BlocProvider.value(
value: context.read<CustomersCubit>(),
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<CustomersCubit, CustomersState>(
builder: (context, state) {
if (state.status == CustomersStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
if (state.customers.isEmpty) {
2026-05-02 15:14:22 +02:00
return const Center(
child: Text(
'Nessun cliente trovato.',
style: TextStyle(color: Colors.grey),
),
);
}
2026-05-02 15:14:22 +02:00
return ListView.builder(
controller: scrollController,
2026-05-02 15:14:22 +02:00
itemCount: state.customers.length,
itemBuilder: (context, index) {
2026-05-02 15:14:22 +02:00
final customer = state.customers[index];
return ListTile(
2026-05-02 15:14:22 +02:00
leading: CircleAvatar(
child: Text(
customer.name.substring(0, 1).toUpperCase(),
),
),
title: Text(
2026-05-02 15:14:22 +02:00
customer.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
2026-05-02 15:14:22 +02:00
),
subtitle: Text(
'${customer.phoneNumber}${customer.email}',
),
onTap: () {
// Aggiorniamo il form tramite il Cubit delle operazioni
context
.read<OperationsCubit>()
.updateOperationFields(
2026-05-02 15:14:22 +02:00
customerId: customer.id, // customer.id
customerDisplayName:
2026-05-02 15:14:22 +02:00
customer.name, // customer.name
);
Navigator.pop(modalContext);
},
);
},
);
},
),
),
],
);
},
);
},
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return BlocConsumer<OperationsCubit, OperationsState>(
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<OperationsCubit>().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<OperationsCubit>().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<String>(
2026-05-02 15:14:22 +02:00
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<OperationsCubit>().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<OperationsCubit>();
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) {
2026-05-02 15:14:22 +02:00
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;
2026-05-02 15:14:22 +02:00
if (q > 1) {
context.read<OperationsCubit>().updateOperationFields(
quantity: q - 1,
);
2026-05-02 15:14:22 +02:00
}
},
),
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<OperationsCubit>().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<OperationsCubit>().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<ProvidersCubit, ProvidersState>(
// <--- 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<OperationsCubit>()
.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<ProductsCubit>().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<OperationsCubit>();
// 1. Recuperiamo la lista dei brand (adatta questo in base a dove tieni i brand nel tuo stato)
final existingBrands = context
.read<ProductsCubit>()
.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<ProductsCubit>(),
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<ProductsCubit, ProductState>(
// <--- 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<OperationsCubit>().updateOperationFields(
modelId:
'id_del_modello_$index', // deviceModel.id
// Assicurati di avere questo campo in _updateOperationFields nel Cubit!
// modelDisplayName: deviceModel.nameWithBrand,
);
Navigator.pop(modalContext);
},
);
},
);
},
),
),
],
);
},
);
},
);
}
}