diff --git a/lib/features/master_data/products/blocs/product_cubit.dart b/lib/features/master_data/products/blocs/product_cubit.dart index 312be0a..17e128a 100644 --- a/lib/features/master_data/products/blocs/product_cubit.dart +++ b/lib/features/master_data/products/blocs/product_cubit.dart @@ -102,4 +102,25 @@ class ProductCubit extends Cubit { ); } } + + Future searchModels(String query) async { + if (query.isEmpty) { + // Se cancella tutto, potresti voler ricaricare i modelli del brand selezionato + // o svuotare la lista. Scegliamo di svuotare per pulizia. + emit(state.copyWith(models: [])); + return; + } + + // Opzionale: emetti loading solo se vuoi mostrare una barretta nella UI + // emit(state.copyWith(status: ProductStatus.loading)); + + try { + final results = await _repository.searchModels(query); + emit(state.copyWith(status: ProductStatus.success, models: results)); + } catch (e) { + emit( + state.copyWith(status: ProductStatus.error, errorMessage: e.toString()), + ); + } + } } diff --git a/lib/features/master_data/products/data/product_repository.dart b/lib/features/master_data/products/data/product_repository.dart index 874854f..d456ae3 100644 --- a/lib/features/master_data/products/data/product_repository.dart +++ b/lib/features/master_data/products/data/product_repository.dart @@ -4,14 +4,14 @@ import '../models/brand_model.dart'; import '../models/model_model.dart'; class ProductRepository { - final SupabaseClient _client = GetIt.I(); + final SupabaseClient _supabase = GetIt.I(); // --- BRAND --- /// Recupera tutti i brand dell'azienda Future> getBrands(String companyId) async { try { - final response = await _client + final response = await _supabase .from('brand') .select() .eq('company_id', companyId) @@ -27,7 +27,7 @@ class ProductRepository { /// Crea o aggiorna un brand Future upsertBrand(BrandModel brand) async { try { - final response = await _client + final response = await _supabase .from('brand') .upsert(brand.toJson()) .select() @@ -44,7 +44,7 @@ class ProductRepository { /// Recupera i modelli di un brand specifico Future> getModelsByBrand(String brandId) async { try { - final response = await _client + final response = await _supabase .from('model') .select() .eq('brand_id', brandId) @@ -61,7 +61,7 @@ class ProductRepository { /// NOTA: name_with_brand verrà gestito dal trigger SQL che hai lanciato! Future upsertModel(ModelModel model) async { try { - final response = await _client + final response = await _supabase .from('model') .upsert(model.toJson()) .select() @@ -78,9 +78,24 @@ class ProductRepository { /// Disattiva un brand o un modello (Soft Delete per non rompere le FK delle operazioni passate) Future toggleActiveStatus(String table, String id, bool status) async { try { - await _client.from(table).update({'is_active': status}).eq('id', id); + await _supabase.from(table).update({'is_active': status}).eq('id', id); } catch (e) { throw 'Errore durante la modifica dello stato'; } } + + Future> searchModels(String query) async { + try { + final response = await _supabase + .from('model') + .select() + .ilike('name_with_brand', '%$query%') // Cerca ovunque nel nome + .eq('is_active', true) + .limit(10); // Non esageriamo con i risultati + + return (response as List).map((m) => ModelModel.fromJson(m)).toList(); + } catch (e) { + throw 'Errore durante la ricerca: $e'; + } + } } diff --git a/lib/features/services/ui/service_form_screen/finance_service_dialog.dart b/lib/features/services/ui/service_form_screen/finance_service_dialog.dart index a368942..b85a52d 100644 --- a/lib/features/services/ui/service_form_screen/finance_service_dialog.dart +++ b/lib/features/services/ui/service_form_screen/finance_service_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; @@ -231,7 +233,55 @@ class _FinanceFormState extends State<_FinanceForm> { String? _selectedProviderId; ModelModel? _selectedModel; int _selectedMonths = 30; // Default richiesto + Timer? _debounce; final TextEditingController _searchController = TextEditingController(); + late DateTime _selectedExpirationDate; + + @override + void initState() { + super.initState(); + final now = DateTime.now(); + _selectedExpirationDate = DateTime( + now.year, + now.month + _selectedMonths, + now.day, + ); // Inizialmente 30 mesi dalla data attuale + } + + void _onSearchChanged(String query) { + if (_debounce?.isActive ?? false) _debounce!.cancel(); + _debounce = Timer(const Duration(milliseconds: 500), () { + context.read().searchModels(query); + }); + } + + // Funzione per aggiornare la data quando si clicca sui segmenti 24, 30, 48 + void _updateExpirationByMonths(int months) { + setState(() { + _selectedMonths = months; + final now = DateTime.now(); + // Calcolo preciso: aggiungiamo i mesi alla data attuale + _selectedExpirationDate = DateTime(now.year, now.month + months, now.day); + }); + } + + // Funzione per il picker manuale + Future _selectManualDate() async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedExpirationDate, + firstDate: DateTime.now(), + lastDate: DateTime.now().add( + const Duration(days: 365 * 10), + ), // Fino a 10 anni + ); + if (picked != null && picked != _selectedExpirationDate) { + setState(() { + _selectedExpirationDate = picked; + _selectedMonths = 0; // Resettiamo i segmenti perché è una data custom + }); + } + } @override Widget build(BuildContext context) { @@ -275,7 +325,7 @@ class _FinanceFormState extends State<_FinanceForm> { ), ), onChanged: (val) { - // Qui andrebbe il debouncing per filtrare i modelli nel Cubit + _onSearchChanged(val); }, ), const SizedBox(height: 8), @@ -315,8 +365,50 @@ class _FinanceFormState extends State<_FinanceForm> { ButtonSegment(value: 48, label: Text("48m")), ], selected: {_selectedMonths}, - onSelectionChanged: (val) => - setState(() => _selectedMonths = val.first), + onSelectionChanged: (val) => _updateExpirationByMonths(val.first), + ), + + const SizedBox(height: 16), + + // RIEPILOGO DATA E PICKER MANUALE (Stile Energia) + const Text( + "Scadenza Finanziamento", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + color: Colors.grey, + ), + ), + const SizedBox(height: 4), + InkWell( + onTap: _selectManualDate, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Icon( + Icons.calendar_today, + size: 18, + color: Colors.blue, + ), + const SizedBox(width: 12), + Text( + "${_selectedExpirationDate.day.toString().padLeft(2, '0')}/${_selectedExpirationDate.month.toString().padLeft(2, '0')}/${_selectedExpirationDate.year}", + style: const TextStyle(fontSize: 16), + ), + ], + ), + const Icon(Icons.edit, size: 18, color: Colors.grey), + ], + ), + ), ), const SizedBox(height: 24),