This commit is contained in:
2026-04-18 12:01:12 +02:00
parent a3509bca91
commit bbb9729ca4
3 changed files with 137 additions and 9 deletions

View File

@@ -102,4 +102,25 @@ class ProductCubit extends Cubit<ProductState> {
); );
} }
} }
Future<void> 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()),
);
}
}
} }

View File

@@ -4,14 +4,14 @@ import '../models/brand_model.dart';
import '../models/model_model.dart'; import '../models/model_model.dart';
class ProductRepository { class ProductRepository {
final SupabaseClient _client = GetIt.I<SupabaseClient>(); final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
// --- BRAND --- // --- BRAND ---
/// Recupera tutti i brand dell'azienda /// Recupera tutti i brand dell'azienda
Future<List<BrandModel>> getBrands(String companyId) async { Future<List<BrandModel>> getBrands(String companyId) async {
try { try {
final response = await _client final response = await _supabase
.from('brand') .from('brand')
.select() .select()
.eq('company_id', companyId) .eq('company_id', companyId)
@@ -27,7 +27,7 @@ class ProductRepository {
/// Crea o aggiorna un brand /// Crea o aggiorna un brand
Future<BrandModel> upsertBrand(BrandModel brand) async { Future<BrandModel> upsertBrand(BrandModel brand) async {
try { try {
final response = await _client final response = await _supabase
.from('brand') .from('brand')
.upsert(brand.toJson()) .upsert(brand.toJson())
.select() .select()
@@ -44,7 +44,7 @@ class ProductRepository {
/// Recupera i modelli di un brand specifico /// Recupera i modelli di un brand specifico
Future<List<ModelModel>> getModelsByBrand(String brandId) async { Future<List<ModelModel>> getModelsByBrand(String brandId) async {
try { try {
final response = await _client final response = await _supabase
.from('model') .from('model')
.select() .select()
.eq('brand_id', brandId) .eq('brand_id', brandId)
@@ -61,7 +61,7 @@ class ProductRepository {
/// NOTA: name_with_brand verrà gestito dal trigger SQL che hai lanciato! /// NOTA: name_with_brand verrà gestito dal trigger SQL che hai lanciato!
Future<ModelModel> upsertModel(ModelModel model) async { Future<ModelModel> upsertModel(ModelModel model) async {
try { try {
final response = await _client final response = await _supabase
.from('model') .from('model')
.upsert(model.toJson()) .upsert(model.toJson())
.select() .select()
@@ -78,9 +78,24 @@ class ProductRepository {
/// Disattiva un brand o un modello (Soft Delete per non rompere le FK delle operazioni passate) /// Disattiva un brand o un modello (Soft Delete per non rompere le FK delle operazioni passate)
Future<void> toggleActiveStatus(String table, String id, bool status) async { Future<void> toggleActiveStatus(String table, String id, bool status) async {
try { 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) { } catch (e) {
throw 'Errore durante la modifica dello stato'; throw 'Errore durante la modifica dello stato';
} }
} }
Future<List<ModelModel>> 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';
}
}
} }

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
@@ -231,7 +233,55 @@ class _FinanceFormState extends State<_FinanceForm> {
String? _selectedProviderId; String? _selectedProviderId;
ModelModel? _selectedModel; ModelModel? _selectedModel;
int _selectedMonths = 30; // Default richiesto int _selectedMonths = 30; // Default richiesto
Timer? _debounce;
final TextEditingController _searchController = TextEditingController(); 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<ProductCubit>().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<void> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -275,7 +325,7 @@ class _FinanceFormState extends State<_FinanceForm> {
), ),
), ),
onChanged: (val) { onChanged: (val) {
// Qui andrebbe il debouncing per filtrare i modelli nel Cubit _onSearchChanged(val);
}, },
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -315,8 +365,50 @@ class _FinanceFormState extends State<_FinanceForm> {
ButtonSegment(value: 48, label: Text("48m")), ButtonSegment(value: 48, label: Text("48m")),
], ],
selected: {_selectedMonths}, selected: {_selectedMonths},
onSelectionChanged: (val) => onSelectionChanged: (val) => _updateExpirationByMonths(val.first),
setState(() => _selectedMonths = 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), const SizedBox(height: 24),