fin ok
This commit is contained in:
@@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import '../models/brand_model.dart';
|
||||
import '../models/model_model.dart';
|
||||
|
||||
class ProductRepository {
|
||||
final SupabaseClient _client = GetIt.I<SupabaseClient>();
|
||||
final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
|
||||
|
||||
// --- BRAND ---
|
||||
|
||||
/// Recupera tutti i brand dell'azienda
|
||||
Future<List<BrandModel>> 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<BrandModel> 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<List<ModelModel>> 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<ModelModel> 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<void> 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<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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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
|
||||
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),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user