diff --git a/lib/features/master_data/staff/data/staff_repository.dart b/lib/features/master_data/staff/data/staff_repository.dart index 4ca0a3e..89032c2 100644 --- a/lib/features/master_data/staff/data/staff_repository.dart +++ b/lib/features/master_data/staff/data/staff_repository.dart @@ -16,17 +16,17 @@ class StaffRepository { .eq('company_id', companyId) .order('name', ascending: true); - return (response as List).map((s) => StaffMemberModel.fromJson(s)).toList(); + return (response as List).map((s) => StaffMemberModel.fromMap(s)).toList(); } Future saveStaffMember(StaffMemberModel member) async { final response = await _supabase .from('staff_member') - .upsert(member.toJson()) + .upsert(member.toMap()) .select() .single(); - return StaffMemberModel.fromJson(response); + return StaffMemberModel.fromMap(response); } // --- LOGICA DI GIUNZIONE (Staff <-> Store) --- @@ -42,7 +42,7 @@ class StaffRepository { .eq('store_id', storeId); return (response as List) - .map((item) => StaffMemberModel.fromJson(item['staff_member'])) + .map((item) => StaffMemberModel.fromMap(item['staff_member'])) .toList(); } diff --git a/lib/features/master_data/staff/models/staff_member_model.dart b/lib/features/master_data/staff/models/staff_member_model.dart index 425d901..46ea409 100644 --- a/lib/features/master_data/staff/models/staff_member_model.dart +++ b/lib/features/master_data/staff/models/staff_member_model.dart @@ -18,20 +18,20 @@ class StaffMemberModel extends Equatable { required this.companyId, }); - factory StaffMemberModel.fromJson(Map json) { + factory StaffMemberModel.fromMap(Map map) { return StaffMemberModel( - id: json['id'], + id: map['id'], // Applichiamo il tuo myFormat per visualizzare i nomi correttamente - name: (json['name'] as String).myFormat(), + name: (map['name'] as String).myFormat(), // L'email la teniamo lowercase per standard tecnico - email: (json['email'] as String? ?? '').toLowerCase().trim(), - phone: (json['phone'] as String? ?? '').trim(), - isActive: json['is_active'] ?? true, - companyId: json['company_id'], + email: (map['email'] as String? ?? '').toLowerCase().trim(), + phone: (map['phone'] as String? ?? '').trim(), + isActive: map['is_active'] ?? true, + companyId: map['company_id'], ); } - Map toJson() { + Map toMap() { return { if (id != null) 'id': id, 'name': name.toLowerCase().trim(), // Salviamo pulito per le query diff --git a/lib/features/master_data/store/bloc/store_cubit.dart b/lib/features/master_data/store/bloc/store_cubit.dart index 8619f58..d0c02ba 100644 --- a/lib/features/master_data/store/bloc/store_cubit.dart +++ b/lib/features/master_data/store/bloc/store_cubit.dart @@ -54,6 +54,58 @@ class StoreCubit extends Cubit { } } + Future assignProviderToStore(String storeId, String providerId) async { + try { + await _repository.associateProviderToStore( + providerId: providerId, + storeId: storeId, + ); + // Dopo l'associazione, potresti voler ricaricare i provider per quel negozio + final updatedProviders = await _repository.fetchProvidersForStore( + storeId, + ); + final newMap = Map>.from( + state.providersByStore, + ); + newMap[storeId] = updatedProviders; + emit(state.copyWith(providersByStore: newMap)); + } catch (e) { + emit( + state.copyWith( + status: StoreStatus.failure, + errorMessage: "Errore nell'associazione: $e", + ), + ); + } + } + + Future removeProviderFromStore( + String storeId, + String providerId, + ) async { + try { + await _repository.removeProviderFromStore( + providerId: providerId, + storeId: storeId, + ); + final updatedProviders = await _repository.fetchProvidersForStore( + storeId, + ); + final newMap = Map>.from( + state.providersByStore, + ); + newMap[storeId] = updatedProviders; + emit(state.copyWith(providersByStore: newMap)); + } catch (e) { + emit( + state.copyWith( + status: StoreStatus.failure, + errorMessage: "Errore nella rimozione: $e", + ), + ); + } + } + Future assignStaffToStore(String storeId, String staffId) async { try { await _staffRepository.assignToStore(staffId, storeId); diff --git a/lib/features/master_data/store/data/store_repository.dart b/lib/features/master_data/store/data/store_repository.dart index 08f254c..07f8468 100644 --- a/lib/features/master_data/store/data/store_repository.dart +++ b/lib/features/master_data/store/data/store_repository.dart @@ -24,8 +24,16 @@ class StoreRepository { .from('store') .select(''' *, - providers_count:providers_in_stores(count), - staff_members_count:staff_in_stores(count) + associated_providers:providers_in_stores ( + provider ( + * + ) + ) + associated_staff:staff_in_stores ( + staff_member ( + * + ) + ) ''') .eq('company_id', companyId) .order('nome'); diff --git a/lib/features/master_data/store/models/store_model.dart b/lib/features/master_data/store/models/store_model.dart index 9186dbb..5f65f86 100644 --- a/lib/features/master_data/store/models/store_model.dart +++ b/lib/features/master_data/store/models/store_model.dart @@ -1,4 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:flux/features/master_data/providers/models/provider_model.dart'; +import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; class StoreModel extends Equatable { final String? id; @@ -11,9 +13,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 + final List associatedProviders; // Provider associati + final List + associatedStaffMembers; // Membri dello staff associati const StoreModel({ this.id, @@ -26,8 +28,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 + this.associatedProviders = const [], + this.associatedStaffMembers = const [], }); // Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza @@ -43,8 +45,8 @@ class StoreModel extends Equatable { cap, comune, provincia, - providersCount, - staffMembersCount, + associatedProviders, + associatedStaffMembers, ]; // Il mitico copyWith per creare nuove istanze modificando solo ciò che serve @@ -59,8 +61,8 @@ class StoreModel extends Equatable { String? cap, String? comune, String? provincia, - int? providersCount, - int? staffMembersCount, + List? associatedProviders, + List? associatedStaffMembers, }) { return StoreModel( id: id ?? this.id, @@ -73,12 +75,36 @@ class StoreModel extends Equatable { cap: cap ?? this.cap, comune: comune ?? this.comune, provincia: provincia ?? this.provincia, - providersCount: providersCount ?? this.providersCount, - staffMembersCount: staffMembersCount ?? this.staffMembersCount, + associatedProviders: associatedProviders ?? this.associatedProviders, + associatedStaffMembers: + associatedStaffMembers ?? this.associatedStaffMembers, ); } factory StoreModel.fromMap(Map map) { + final providersPivotList = map['associated_providers'] as List?; + List providers = []; + if (providersPivotList != null) { + providers = providersPivotList + .where((item) => item['provider'] != null) // Sicurezza + .map( + (item) => + ProviderModel.fromMap(item['provider'] as Map), + ) + .toList(); + } + final staffPivotList = map['associated_staff'] as List?; + List staffMembers = []; + if (staffPivotList != null) { + staffMembers = staffPivotList + .where((item) => item['staff_member'] != null) // Sicurezza + .map( + (item) => StaffMemberModel.fromMap( + item['staff_member'] as Map, + ), + ) + .toList(); + } return StoreModel( id: map['id'] as String, nome: map['nome'], @@ -92,15 +118,8 @@ class StoreModel extends Equatable { 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, + associatedProviders: providers, + associatedStaffMembers: staffMembers, ); } diff --git a/lib/features/master_data/store/ui/stores_screen.dart b/lib/features/master_data/store/ui/stores_screen.dart index 5b23524..963d218 100644 --- a/lib/features/master_data/store/ui/stores_screen.dart +++ b/lib/features/master_data/store/ui/stores_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_bloc.dart'; import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/widgets/flux_text_field.dart'; +import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; import 'package:flux/features/master_data/store/models/store_model.dart'; @@ -94,13 +95,26 @@ class _StoresScreenState extends State { builder: (context, storeState) { final staffCount = storeState.staffByStore[store.id]?.length ?? 0; - return ActionChip( - avatar: const Icon(Icons.people, size: 16), - label: Text("$staffCount Dipendenti"), - onPressed: () => _manageStoreStaff(store), + return Row( + children: [ + ActionChip( + avatar: const Icon(Icons.people, size: 16), + label: Text("$staffCount Dipendenti"), + onPressed: () => _manageStoreStaff(store), + ), + const SizedBox(width: 16), + ActionChip( + avatar: const Icon(Icons.handshake, size: 16), + label: Text("${store.providersCount} Providers"), + onPressed: () { + // Potresti voler navigare alla lista dei provider associati a questo store + }, + ), + ], ); }, ), + const SizedBox(width: 16), TextButton.icon( onPressed: () => _openStoreForm(context, store: store), icon: const Icon(Icons.edit, size: 18), @@ -171,6 +185,51 @@ class _StoresScreenState extends State { ); } + void _manageStoreProviders(StoreModel store) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => BlocBuilder( + builder: (context, state) { + // Qui dentro hai già tutta la lista dei provider e quelli associati a questo store + // Puoi fare una UI simile a quella dello staff, con una checkbox per ogni provider + return Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Providers di ${store.nome}", style: context.titleLarge), + const SizedBox(height: 16), + ...state.allProviders.map((provider) { + final isAssociated = state.associatedIds.contains( + provider.id, + ); + return CheckboxListTile( + title: Text(provider.nome), + value: isAssociated, + onChanged: (selected) { + if (selected == true) { + context.read().assignProviderToStore( + store.id!, + provider.id!, + ); + } else { + context.read().removeProviderFromStore( + provider.id!, + store.id!, + ); + } + }, + ); + }), + ], + ), + ); + }, + ), + ); + } + void _openStoreForm(BuildContext context, {StoreModel? store}) { final nomeController = TextEditingController(text: store?.nome); final indirizzoController = TextEditingController(text: store?.indirizzo);