diff --git a/lib/core/blocs/session/session_bloc.dart b/lib/core/blocs/session/session_bloc.dart index e4eea39..3ea9e2e 100644 --- a/lib/core/blocs/session/session_bloc.dart +++ b/lib/core/blocs/session/session_bloc.dart @@ -71,9 +71,7 @@ class SessionBloc extends Bloc { ); return; } - final availableStores = stores - .map((s) => StoreModel.fromJson(s)) - .toList(); + final availableStores = stores.map((s) => StoreModel.fromMap(s)).toList(); // 3. Tutto ok, gestiamo le SharedPreferences per il negozio final prefs = GetIt.I.get(); @@ -84,7 +82,7 @@ class SessionBloc extends Bloc { lastStoreId = stores.first['id']; await prefs.setString('last_store_id', lastStoreId!); } - final selectedStore = StoreModel.fromJson( + final selectedStore = StoreModel.fromMap( stores.firstWhere((s) => s['id'] == lastStoreId), ); emit( diff --git a/lib/features/master_data/providers/data/provider_repository.dart b/lib/features/master_data/providers/data/provider_repository.dart index 3f21fcf..ef5e271 100644 --- a/lib/features/master_data/providers/data/provider_repository.dart +++ b/lib/features/master_data/providers/data/provider_repository.dart @@ -40,29 +40,22 @@ class ProviderRepository { // Recupera tutti i provider di una company (per la lista generale) Future> fetchAllCompanyProviders(String companyId) async { try { - // La magia è qui: selezioniamo tutto e chiediamo il conteggio (count) - // della tabella pivot providers_in_stores final response = await _supabase .from('provider') .select(''' *, - providers_in_stores(count) + associated_stores:providers_in_stores ( + store ( + * + ) + ) ''') .eq('company_id', companyId) .order('nome'); - return (response as List).map((m) { - // Estraiamo il conteggio dalla struttura restituita da Supabase - // La risposta per ogni riga sarà tipo: { "id": "...", "providers_in_stores": [{"count": 5}] } - final storesList = m['providers_in_stores'] as List?; - final count = (storesList != null && storesList.isNotEmpty) - ? storesList[0]['count'] as int - : 0; - - return ProviderModel.fromMap(m).copyWith(storesCount: count); - }).toList(); + return (response as List).map((m) => ProviderModel.fromMap(m)).toList(); } catch (e) { - throw Exception('Errore fetch all providers: $e'); + throw 'Errore fetch providers: $e'; } } diff --git a/lib/features/master_data/providers/models/provider_model.dart b/lib/features/master_data/providers/models/provider_model.dart index 716bdc3..0e2eddd 100644 --- a/lib/features/master_data/providers/models/provider_model.dart +++ b/lib/features/master_data/providers/models/provider_model.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flux/features/master_data/store/models/store_model.dart'; class ProviderModel extends Equatable { final String? id; @@ -11,7 +12,7 @@ class ProviderModel extends Equatable { final bool altro; final bool isActive; final String companyId; - final int storesCount; + final List associatedStores; const ProviderModel({ this.id, @@ -24,10 +25,21 @@ class ProviderModel extends Equatable { required this.altro, required this.isActive, required this.companyId, - this.storesCount = 0, // Numero di store associati, default a 0 + this.associatedStores = const [], }); factory ProviderModel.fromMap(Map map) { + // Estraiamo la lista dalla pivot e poi prendiamo l'oggetto 'store' annidato + final pivotList = map['associated_stores'] as List?; + List stores = []; + if (pivotList != null) { + stores = pivotList + .where((item) => item['store'] != null) // Sicurezza + .map( + (item) => StoreModel.fromMap(item['store'] as Map), + ) + .toList(); + } return ProviderModel( id: map['id'], nome: map['nome'], @@ -39,11 +51,7 @@ class ProviderModel extends Equatable { altro: map['altro'] ?? false, isActive: map['is_active'] ?? true, companyId: map['company_id'], - storesCount: - map['providers_in_stores'] != null && - map['providers_in_stores'].isNotEmpty - ? map['providers_in_stores'][0]['count'] as int - : 0, // Assumiamo che l'API possa restituire questo campo + associatedStores: stores, ); } @@ -79,7 +87,7 @@ class ProviderModel extends Equatable { altro, isActive, companyId, - storesCount, + associatedStores, ]; ProviderModel copyWith({ @@ -93,7 +101,7 @@ class ProviderModel extends Equatable { bool? altro, bool? isActive, String? companyId, - int? storesCount, + List? associatedStores, }) { return ProviderModel( id: id ?? this.id, @@ -106,7 +114,7 @@ class ProviderModel extends Equatable { altro: altro ?? this.altro, isActive: isActive ?? this.isActive, companyId: companyId ?? this.companyId, - storesCount: storesCount ?? this.storesCount, + associatedStores: associatedStores ?? this.associatedStores, ); } } diff --git a/lib/features/master_data/providers/ui/provider_form_sheet.dart b/lib/features/master_data/providers/ui/provider_form_sheet.dart index 1da2781..5a3e4b7 100644 --- a/lib/features/master_data/providers/ui/provider_form_sheet.dart +++ b/lib/features/master_data/providers/ui/provider_form_sheet.dart @@ -29,6 +29,9 @@ class _ProviderFormSheetState extends State { void initState() { super.initState(); final p = widget.initialProvider; + for (final store in p?.associatedStores ?? []) { + _tempSelectedStoreIds.add(store.id!); + } _nameController = TextEditingController(text: p?.nome ?? ''); _telefoniaFissa = p?.telefoniaFissa ?? false; _telefoniaMobile = p?.telefoniaMobile ?? false; diff --git a/lib/features/master_data/providers/ui/providers_master_data_screen.dart b/lib/features/master_data/providers/ui/providers_master_data_screen.dart index d88ba22..70479ff 100644 --- a/lib/features/master_data/providers/ui/providers_master_data_screen.dart +++ b/lib/features/master_data/providers/ui/providers_master_data_screen.dart @@ -126,7 +126,7 @@ class _ProvidersMasterDataScreenState extends State { // Un piccolo testo che indica il numero di store associati // Nota: Dovrai assicurarti che il Cubit carichi queste info return Text( - "Disponibile in ${provider.storesCount} negozi", + "Disponibile in ${provider.associatedStores.length} negozi", style: TextStyle( fontSize: 11, color: Colors.indigo.withValues(alpha: 0.7), diff --git a/lib/features/master_data/staff/data/staff_repository.dart b/lib/features/master_data/staff/data/staff_repository.dart index dad6dc0..4ca0a3e 100644 --- a/lib/features/master_data/staff/data/staff_repository.dart +++ b/lib/features/master_data/staff/data/staff_repository.dart @@ -57,7 +57,7 @@ class StaffRepository { .eq('staff_member_id', staffId); return (response as List) - .map((item) => StoreModel.fromJson(item['store'])) + .map((item) => StoreModel.fromMap(item['store'])) .toList(); } diff --git a/lib/features/master_data/store/bloc/store_cubit.dart b/lib/features/master_data/store/bloc/store_cubit.dart index b6bfc30..8619f58 100644 --- a/lib/features/master_data/store/bloc/store_cubit.dart +++ b/lib/features/master_data/store/bloc/store_cubit.dart @@ -31,7 +31,7 @@ class StoreCubit extends Cubit { Future loadStores() async { emit(state.copyWith(status: StoreStatus.loading)); try { - final stores = await _repository.getStoresByCompany( + final stores = await _repository.fetchAllCompanyStores( _sessionBloc.state.company!.id, ); final Map> staffByStore = {}; diff --git a/lib/features/master_data/store/data/store_repository.dart b/lib/features/master_data/store/data/store_repository.dart index 96487b7..08f254c 100644 --- a/lib/features/master_data/store/data/store_repository.dart +++ b/lib/features/master_data/store/data/store_repository.dart @@ -3,12 +3,12 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/store_model.dart'; class StoreRepository { - final SupabaseClient _client = GetIt.I.get(); + final SupabaseClient _supabase = GetIt.I.get(); /// Crea un nuovo negozio associato alla compagnia dell'utente Future createStore(StoreModel store) async { try { - await _client.from('store').insert(store.toJson()); + await _supabase.from('store').insert(store.toMap()); } on PostgrestException catch (e) { // Intercettiamo errori specifici del database throw e.message; @@ -18,19 +18,21 @@ class StoreRepository { } /// Recupera tutti i negozi di una determinata compagnia - Future> getStoresByCompany(String companyId) async { + Future> fetchAllCompanyStores(String companyId) async { try { - final response = await _client + final response = await _supabase .from('store') - .select() + .select(''' + *, + providers_count:providers_in_stores(count), + staff_members_count:staff_in_stores(count) + ''') .eq('company_id', companyId) - .order('created_at'); + .order('nome'); - return (response as List) - .map((json) => StoreModel.fromJson(json)) - .toList(); + return (response as List).map((m) => StoreModel.fromMap(m)).toList(); } catch (e) { - throw 'Errore nel recupero dei negozi'; + throw 'Errore nel recupero dei negozi: $e'; } } } diff --git a/lib/features/master_data/store/models/store_model.dart b/lib/features/master_data/store/models/store_model.dart index 680e2f2..9186dbb 100644 --- a/lib/features/master_data/store/models/store_model.dart +++ b/lib/features/master_data/store/models/store_model.dart @@ -11,6 +11,9 @@ class StoreModel extends Equatable { final String cap; final String comune; final String provincia; + final int providersCount; // Numero di provider associati, utile per la lista + final int + staffMembersCount; // Numero di membri dello staff associati, utile per la lista const StoreModel({ this.id, @@ -23,6 +26,8 @@ class StoreModel extends Equatable { required this.cap, required this.comune, required this.provincia, + this.providersCount = 0, // Default a 0 se non specificato + this.staffMembersCount = 0, // Default a 0 se non specificato }); // Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza @@ -38,6 +43,8 @@ class StoreModel extends Equatable { cap, comune, provincia, + providersCount, + staffMembersCount, ]; // Il mitico copyWith per creare nuove istanze modificando solo ciò che serve @@ -52,6 +59,8 @@ class StoreModel extends Equatable { String? cap, String? comune, String? provincia, + int? providersCount, + int? staffMembersCount, }) { return StoreModel( id: id ?? this.id, @@ -64,27 +73,38 @@ class StoreModel extends Equatable { cap: cap ?? this.cap, comune: comune ?? this.comune, provincia: provincia ?? this.provincia, + providersCount: providersCount ?? this.providersCount, + staffMembersCount: staffMembersCount ?? this.staffMembersCount, ); } - factory StoreModel.fromJson(Map json) { + factory StoreModel.fromMap(Map map) { return StoreModel( - id: json['id'] as String, - nome: json['nome'], - companyId: json['company_id'] as String, - isActive: json['is_active'] ?? true, - isPaid: json['is_paid'] ?? false, - paymentExpiration: json['payment_expiration'] != null - ? DateTime.parse(json['payment_expiration']) + id: map['id'] as String, + nome: map['nome'], + companyId: map['company_id'] as String, + isActive: map['is_active'] ?? true, + isPaid: map['is_paid'] ?? false, + paymentExpiration: map['payment_expiration'] != null + ? DateTime.parse(map['payment_expiration']) : null, - indirizzo: json['indirizzo'], - cap: json['cap'], - comune: json['comune'], - provincia: json['provincia'], + indirizzo: map['indirizzo'], + cap: map['cap'], + comune: map['comune'], + provincia: map['provincia'], + providersCount: + map['providers_count'] != null && map['providers_count'].isNotEmpty + ? map['providers_count'][0]['count'] as int + : 0, + staffMembersCount: + map['staff_members_count'] != null && + map['staff_members_count'].isNotEmpty + ? map['staff_members_count'][0]['count'] as int + : 0, ); } - Map toJson() { + Map toMap() { return { if (id != null) 'id': id, 'nome': nome,