feat-insert-service #5
@@ -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';
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user