From 22a4f1dac4487f7588627a71b2faf4d6e471eb35 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Fri, 17 Apr 2026 11:36:15 +0200 Subject: [PATCH 1/3] Refactor provider and store models to use fromMap method; update associated stores handling in UI --- lib/core/blocs/session/session_bloc.dart | 6 +-- .../providers/data/provider_repository.dart | 21 +++------ .../providers/models/provider_model.dart | 28 +++++++---- .../providers/ui/provider_form_sheet.dart | 3 ++ .../ui/providers_master_data_screen.dart | 2 +- .../staff/data/staff_repository.dart | 2 +- .../master_data/store/bloc/store_cubit.dart | 2 +- .../store/data/store_repository.dart | 22 +++++---- .../master_data/store/models/store_model.dart | 46 +++++++++++++------ 9 files changed, 78 insertions(+), 54 deletions(-) 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, -- 2.43.0 From 08a521c21cbf03abd60219a141eedce965f31644 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Fri, 17 Apr 2026 12:40:58 +0200 Subject: [PATCH 2/3] Refactor Staff and Store models to use fromMap method; enhance StoreCubit with provider management functionality --- .../staff/data/staff_repository.dart | 8 +-- .../staff/models/staff_member_model.dart | 16 ++--- .../master_data/store/bloc/store_cubit.dart | 52 ++++++++++++++ .../store/data/store_repository.dart | 12 +++- .../master_data/store/models/store_model.dart | 59 ++++++++++------ .../master_data/store/ui/stores_screen.dart | 67 +++++++++++++++++-- 6 files changed, 176 insertions(+), 38 deletions(-) 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); -- 2.43.0 From f4d59a2f4f8df088c58955000735a8714e4afad5 Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Fri, 17 Apr 2026 14:58:29 +0200 Subject: [PATCH 3/3] Refactor Store management: implement save and sync functionality for providers and staff; create StoreCard and StoreForm components --- .../ui/providers_master_data_screen.dart | 2 - .../master_data/store/bloc/store_cubit.dart | 87 +++-- .../store/data/store_repository.dart | 91 +++++ .../master_data/store/ui/store_card.dart | 232 +++++++++++++ .../master_data/store/ui/store_form.dart | 157 +++++++++ .../master_data/store/ui/stores_screen.dart | 311 +----------------- lib/main.dart | 3 +- 7 files changed, 537 insertions(+), 346 deletions(-) create mode 100644 lib/features/master_data/store/ui/store_card.dart create mode 100644 lib/features/master_data/store/ui/store_form.dart 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 70479ff..bc69fe0 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 @@ -17,8 +17,6 @@ class _ProvidersMasterDataScreenState extends State { @override void initState() { super.initState(); - // Carichiamo i provider della company (senza store specifico per ora) - context.read().loadProviders(null); } @override diff --git a/lib/features/master_data/store/bloc/store_cubit.dart b/lib/features/master_data/store/bloc/store_cubit.dart index d0c02ba..b36c87c 100644 --- a/lib/features/master_data/store/bloc/store_cubit.dart +++ b/lib/features/master_data/store/bloc/store_cubit.dart @@ -1,6 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/master_data/staff/data/staff_repository.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; import 'package:flux/features/master_data/store/data/store_repository.dart'; @@ -54,21 +55,56 @@ class StoreCubit extends Cubit { } } - Future assignProviderToStore(String storeId, String providerId) async { + Future saveProvidersForStore( + StoreModel store, + List providers, + ) async { + emit(state.copyWith(status: StoreStatus.loading)); + try { - await _repository.associateProviderToStore( + final savedStore = await _repository.saveStore(store); + await _repository.syncStoreProviders(savedStore.id!, providers); + await loadStores(); + } catch (e) { + emit( + state.copyWith( + status: StoreStatus.failure, + errorMessage: "Errore nel salvataggio dei provider: $e", + ), + ); + } + } + + Future saveStaffForStore( + StoreModel store, + List staffMembers, + ) async { + emit(state.copyWith(status: StoreStatus.loading)); + + try { + final savedStore = await _repository.saveStore(store); + await _repository.syncStoreStaff(savedStore.id!, staffMembers); + await loadStores(); + } catch (e) { + emit( + state.copyWith( + status: StoreStatus.failure, + errorMessage: "Errore nel salvataggio dello staff: $e", + ), + ); + } + } + + Future associateStoreToProvider( + String storeId, + String providerId, + ) async { + try { + await _repository.associateStoreToProvider( 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)); + loadStores(); } catch (e) { emit( state.copyWith( @@ -84,18 +120,11 @@ class StoreCubit extends Cubit { String providerId, ) async { try { - await _repository.removeProviderFromStore( + await _repository.removeStoreFromProvider( providerId: providerId, storeId: storeId, ); - final updatedProviders = await _repository.fetchProvidersForStore( - storeId, - ); - final newMap = Map>.from( - state.providersByStore, - ); - newMap[storeId] = updatedProviders; - emit(state.copyWith(providersByStore: newMap)); + loadStores(); } catch (e) { emit( state.copyWith( @@ -110,14 +139,7 @@ class StoreCubit extends Cubit { try { await _staffRepository.assignToStore(staffId, storeId); // Dopo l'assegnazione, potresti voler ricaricare lo staff per quel negozio - final updatedStaff = await _staffRepository.getStaffMembersInStore( - storeId, - ); - final newMap = Map>.from( - state.staffByStore, - ); - newMap[storeId] = updatedStaff; - emit(state.copyWith(status: StoreStatus.success, staffByStore: newMap)); + loadStores(); } catch (e) { emit( state.copyWith(status: StoreStatus.failure, errorMessage: e.toString()), @@ -129,14 +151,7 @@ class StoreCubit extends Cubit { Future removeStaffFromStore(String staffId, String storeId) async { try { await _staffRepository.removeFromStore(staffId, storeId); - final updatedStaff = await _staffRepository.getStaffMembersInStore( - storeId, - ); - final newMap = Map>.from( - state.staffByStore, - ); - newMap[storeId] = updatedStaff; - emit(state.copyWith(staffByStore: newMap)); + loadStores(); } catch (e) { emit( state.copyWith( diff --git a/lib/features/master_data/store/data/store_repository.dart b/lib/features/master_data/store/data/store_repository.dart index 07f8468..01e52c1 100644 --- a/lib/features/master_data/store/data/store_repository.dart +++ b/lib/features/master_data/store/data/store_repository.dart @@ -1,3 +1,5 @@ +import 'package:flux/features/master_data/providers/models/provider_model.dart'; +import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; import 'package:get_it/get_it.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/store_model.dart'; @@ -17,6 +19,66 @@ class StoreRepository { } } + Future saveStore(StoreModel store) async { + try { + final response = await _supabase + .from('store') + .upsert(store.toMap()) + .select() + .single(); + return StoreModel.fromMap(response); + } on PostgrestException catch (e) { + throw e.message; + } catch (e) { + throw 'Errore imprevisto durante il salvataggio del negozio: $e'; + } + } + + Future syncStoreProviders( + String storeId, + List providers, + ) async { + try { + // 1. Eliminiamo tutte le associazioni correnti per questo negozio + await _supabase + .from('providers_in_stores') + .delete() + .eq('store_id', storeId); + + // 2. Se ci sono nuovi provider da associare, li inseriamo + if (providers.isNotEmpty) { + final inserts = providers + .map((p) => {'store_id': storeId, 'provider_id': p.id}) + .toList(); + + await _supabase.from('providers_in_stores').insert(inserts); + } + } catch (e) { + throw 'Errore durante la sincronizzazione provider: $e'; + } + } + + Future syncStoreStaff( + String storeId, + List staffMembers, + ) async { + try { + // 1. Eliminiamo tutte le associazioni correnti per questo negozio + await _supabase.from('staff_in_stores').delete().eq('store_id', storeId); + + // 2. Se ci sono nuovi dipendenti da associare, li inseriamo + if (staffMembers.isNotEmpty) { + final inserts = staffMembers + .map((s) => {'store_id': storeId, 'staff_id': s.id}) + .toList(); + + await _supabase.from('staff_in_stores').insert(inserts); + } + } catch (e) { + throw 'Errore durante la sincronizzazione staff: $e'; + } + } + /// Recupera tutti i negozi di una determinata compagnia Future> fetchAllCompanyStores(String companyId) async { try { @@ -43,4 +105,33 @@ class StoreRepository { throw 'Errore nel recupero dei negozi: $e'; } } + + Future associateStoreToProvider({ + required String storeId, + required String providerId, + }) async { + try { + await _supabase.from('providers_in_stores').insert({ + 'store_id': storeId, + 'provider_id': providerId, + }); + } catch (e) { + throw 'Errore durante l\'associazione del negozio al provider: $e'; + } + } + + Future removeStoreFromProvider({ + required String storeId, + required String providerId, + }) async { + try { + await _supabase + .from('providers_in_stores') + .delete() + .eq('store_id', storeId) + .eq('provider_id', providerId); + } catch (e) { + throw 'Errore durante la rimozione del negozio dal provider: $e'; + } + } } diff --git a/lib/features/master_data/store/ui/store_card.dart b/lib/features/master_data/store/ui/store_card.dart new file mode 100644 index 0000000..910c3c3 --- /dev/null +++ b/lib/features/master_data/store/ui/store_card.dart @@ -0,0 +1,232 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/theme/theme.dart'; +import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart'; +import 'package:flux/features/master_data/providers/models/provider_model.dart'; +import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; +import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; +import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; +import 'package:flux/features/master_data/store/models/store_model.dart'; +import 'package:flux/features/master_data/store/ui/store_form.dart'; + +class StoreCard extends StatefulWidget { + final StoreModel store; + const StoreCard({super.key, required this.store}); + + @override + State createState() => _StoreCardState(); +} + +class _StoreCardState extends State { + late List _tempAssociatedProviders; + late List _tempAssociatedStaff; + + @override + void initState() { + _tempAssociatedProviders = widget.store.associatedProviders; + _tempAssociatedStaff = widget.store.associatedStaffMembers; + super.initState(); + } + + void _save() { + final storeCubit = context.read(); + storeCubit.saveProvidersForStore(widget.store, _tempAssociatedProviders); + } + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide( + color: widget.store.isActive + ? Colors.transparent + : Colors.red.withValues(alpha: 0.3), + ), + ), + child: Column( + children: [ + ListTile( + leading: Icon( + Icons.storefront, + color: widget.store.isActive ? context.accent : Colors.grey, + ), + title: Text( + widget.store.nome, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + "${widget.store.comune} (${widget.store.provincia}) - ${widget.store.indirizzo}", + ), + trailing: Switch( + value: widget.store.isActive, + onChanged: (val) { + // context.read().add(ToggleStoreStatus(store.id, val)); + }, + ), + ), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Mostra quanti dipendenti ci sono (usando lo StaffCubit) + BlocBuilder( + builder: (context, storeState) { + final staffCount = + storeState.staffByStore[widget.store.id]?.length ?? 0; + return Row( + children: [ + ActionChip( + avatar: const Icon(Icons.people, size: 16), + label: Text("$staffCount Dipendenti"), + onPressed: () => _manageStoreStaff(widget.store), + ), + const SizedBox(width: 16), + ActionChip( + avatar: const Icon(Icons.handshake, size: 16), + label: Text( + "${widget.store.associatedProviders.length} Providers", + ), + onPressed: () => _manageStoreProviders(widget.store), + ), + ], + ); + }, + ), + const SizedBox(width: 16), + TextButton.icon( + onPressed: () => _openStoreForm(context, store: widget.store), + icon: const Icon(Icons.edit, size: 18), + label: const Text("Modifica"), + ), + ], + ), + ), + ], + ), + ); + } + + void _manageStoreStaff(StoreModel store) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => BlocBuilder( + // 1. Prendi TUTTI i dipendenti + builder: (context, staffState) { + return BlocBuilder( + // 2. Prendi le ASSEGNAZIONI + builder: (context, storeState) { + final assignedToThisStore = + storeState.staffByStore[store.id!] ?? []; + + return Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Personale di ${store.nome}", + style: context.titleLarge, + ), + const SizedBox(height: 16), + ...staffState.allStaff.map((person) { + // La spunta deve dipendere dallo StoreCubit! + final bool isAssigned = assignedToThisStore.any( + (s) => s.id == person.id, + ); + + return CheckboxListTile( + title: Text(person.name), + value: isAssigned, + onChanged: (selected) { + if (selected == true) { + _tempAssociatedStaff.add(person); + } else { + _tempAssociatedStaff.removeWhere( + (s) => s.id == person.id, + ); + } + }, + ); + }), + const SizedBox(height: 24), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(50), + ), + onPressed: _save, + child: const Text('SALVA NEGOZIO'), + ), + const SizedBox(height: 24), + ], + ), + ); + }, + ); + }, + ), + ); + } + + void _manageStoreProviders(StoreModel store) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + + builder: (context) => BlocBuilder( + builder: (context, state) { + 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 = _tempAssociatedProviders.any( + (p) => p.id == provider.id, + ); + return CheckboxListTile( + title: Text(provider.nome), + value: isAssociated, + onChanged: (selected) { + if (selected == true) { + _tempAssociatedProviders.add(provider); + } else { + _tempAssociatedProviders.removeWhere( + (p) => p.id == provider.id, + ); + } + }, + ); + }), + const SizedBox(height: 24), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(50), + ), + onPressed: _save, + child: const Text('SALVA NEGOZIO'), + ), + const SizedBox(height: 24), + ], + ), + ); + }, + ), + ); + } + + void _openStoreForm(BuildContext context, {StoreModel? store}) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => StoreForm(store: store), + ); + } +} diff --git a/lib/features/master_data/store/ui/store_form.dart b/lib/features/master_data/store/ui/store_form.dart new file mode 100644 index 0000000..ee0b88b --- /dev/null +++ b/lib/features/master_data/store/ui/store_form.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/core/widgets/flux_text_field.dart'; +import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; +import 'package:flux/features/master_data/store/models/store_model.dart'; + +class StoreForm extends StatefulWidget { + final StoreModel? store; // Se è null, stiamo creando un nuovo negozio + const StoreForm({super.key, this.store}); + + @override + State createState() => _StoreFormState(); +} + +class _StoreFormState extends State { + final nomeController = TextEditingController(); + final indirizzoController = TextEditingController(); + final capController = TextEditingController(); + final comuneController = TextEditingController(); + final provinciaController = TextEditingController(); + + @override + void initState() { + super.initState(); + if (widget.store != null) { + nomeController.text = widget.store!.nome; + indirizzoController.text = widget.store!.indirizzo; + capController.text = widget.store!.cap; + comuneController.text = widget.store!.comune; + provinciaController.text = widget.store!.provincia; + } + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + padding: EdgeInsets.only( + top: 24, + left: 24, + right: 24, + bottom: MediaQuery.of(context).viewInsets.bottom + 24, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.store == null ? "Nuovo Punto Vendita" : "Modifica Negozio", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 24), + + // --- DATI PRINCIPALI --- + FluxTextField( + controller: nomeController, + label: "Nome Negozio (es. Flux Milano)", + icon: Icons.storefront_rounded, + keyboardType: TextInputType.name, + ), + const SizedBox(height: 16), + FluxTextField( + controller: indirizzoController, + label: "Indirizzo", + icon: Icons.map_outlined, + keyboardType: TextInputType.streetAddress, + ), + const SizedBox(height: 16), + + // --- CAP, COMUNE, PROVINCIA (In riga) --- + Row( + children: [ + Expanded( + flex: 2, + child: FluxTextField( + controller: capController, + label: "CAP", + icon: Icons.post_add_rounded, + keyboardType: TextInputType.number, + maxLength: 5, + ), + ), + const SizedBox(width: 8), + Expanded( + flex: 4, + child: FluxTextField( + controller: comuneController, + label: "Comune", + icon: Icons.location_city_rounded, + keyboardType: TextInputType.name, + ), + ), + const SizedBox(width: 8), + Expanded( + flex: 2, + child: FluxTextField( + controller: provinciaController, + label: "Prov", + icon: Icons.explore_outlined, + keyboardType: TextInputType.name, + onChanged: (value) => value.toUpperCase(), + maxLength: 2, + ), + ), + ], + ), + + const SizedBox(height: 32), + + // --- TASTO SALVA --- + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: () { + if (nomeController.text.isEmpty) return; + + final storeData = StoreModel( + id: widget + .store + ?.id, // Se nullo, Supabase ne crea uno nuovo + nome: nomeController.text, + indirizzo: indirizzoController.text, + cap: capController.text, + comune: comuneController.text, + provincia: provinciaController.text, + companyId: context + .read() + .state + .company! + .id, // Recuperiamo la companyId + isActive: widget.store?.isActive ?? true, + isPaid: widget.store?.isPaid ?? false, + paymentExpiration: widget.store?.paymentExpiration, + ); + + // Chiamata al Bloc per il salvataggio + context.read().createStore(storeData); + + Navigator.pop(context); + }, + child: Text( + widget.store == null ? "CREA NEGOZIO" : "AGGIORNA DATI", + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/master_data/store/ui/stores_screen.dart b/lib/features/master_data/store/ui/stores_screen.dart index 963d218..e6b4544 100644 --- a/lib/features/master_data/store/ui/stores_screen.dart +++ b/lib/features/master_data/store/ui/stores_screen.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; 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'; +import 'package:flux/features/master_data/store/ui/store_card.dart'; +import 'package:flux/features/master_data/store/ui/store_form.dart'; class StoresScreen extends StatefulWidget { const StoresScreen({super.key}); @@ -39,7 +37,7 @@ class _StoresScreenState extends State { itemCount: state.stores.length, itemBuilder: (context, index) { final store = state.stores[index]; - return _buildStoreCard(store); + return StoreCard(store: store); }, ); }, @@ -52,313 +50,12 @@ class _StoresScreenState extends State { ); } - Widget _buildStoreCard(StoreModel store) { - return Card( - margin: const EdgeInsets.only(bottom: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide( - color: store.isActive - ? Colors.transparent - : Colors.red.withValues(alpha: 0.3), - ), - ), - child: Column( - children: [ - ListTile( - leading: Icon( - Icons.storefront, - color: store.isActive ? context.accent : Colors.grey, - ), - title: Text( - store.nome, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text( - "${store.comune} (${store.provincia}) - ${store.indirizzo}", - ), - trailing: Switch( - value: store.isActive, - onChanged: (val) { - // context.read().add(ToggleStoreStatus(store.id, val)); - }, - ), - ), - const Divider(height: 1), - Padding( - padding: const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Mostra quanti dipendenti ci sono (usando lo StaffCubit) - BlocBuilder( - builder: (context, storeState) { - final staffCount = - storeState.staffByStore[store.id]?.length ?? 0; - 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), - label: const Text("Modifica"), - ), - ], - ), - ), - ], - ), - ); - } - - void _manageStoreStaff(StoreModel store) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => BlocBuilder( - // 1. Prendi TUTTI i dipendenti - builder: (context, staffState) { - return BlocBuilder( - // 2. Prendi le ASSEGNAZIONI - builder: (context, storeState) { - final assignedToThisStore = - storeState.staffByStore[store.id!] ?? []; - - return Container( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Personale di ${store.nome}", - style: context.titleLarge, - ), - const SizedBox(height: 16), - ...staffState.allStaff.map((person) { - // La spunta deve dipendere dallo StoreCubit! - final bool isAssigned = assignedToThisStore.any( - (s) => s.id == person.id, - ); - - return CheckboxListTile( - title: Text(person.name), - value: isAssigned, - onChanged: (selected) { - if (selected == true) { - context.read().assignStaffToStore( - store.id!, - person.id!, - ); - } else { - context.read().removeStaffFromStore( - person.id!, - store.id!, - ); - } - }, - ); - }), - ], - ), - ); - }, - ); - }, - ), - ); - } - - 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); - final capController = TextEditingController(text: store?.cap); - final comuneController = TextEditingController(text: store?.comune); - final provinciaController = TextEditingController(text: store?.provincia); - showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, - builder: (context) => Container( - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - ), - padding: EdgeInsets.only( - top: 24, - left: 24, - right: 24, - bottom: MediaQuery.of(context).viewInsets.bottom + 24, - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - store == null ? "Nuovo Punto Vendita" : "Modifica Negozio", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 24), - - // --- DATI PRINCIPALI --- - FluxTextField( - controller: nomeController, - label: "Nome Negozio (es. Flux Milano)", - icon: Icons.storefront_rounded, - keyboardType: TextInputType.name, - ), - const SizedBox(height: 16), - FluxTextField( - controller: indirizzoController, - label: "Indirizzo", - icon: Icons.map_outlined, - keyboardType: TextInputType.streetAddress, - ), - const SizedBox(height: 16), - - // --- CAP, COMUNE, PROVINCIA (In riga) --- - Row( - children: [ - Expanded( - flex: 2, - child: FluxTextField( - controller: capController, - label: "CAP", - icon: Icons.post_add_rounded, - keyboardType: TextInputType.number, - maxLength: 5, - ), - ), - const SizedBox(width: 8), - Expanded( - flex: 4, - child: FluxTextField( - controller: comuneController, - label: "Comune", - icon: Icons.location_city_rounded, - keyboardType: TextInputType.name, - ), - ), - const SizedBox(width: 8), - Expanded( - flex: 2, - child: FluxTextField( - controller: provinciaController, - label: "Prov", - icon: Icons.explore_outlined, - keyboardType: TextInputType.name, - onChanged: (value) => value.toUpperCase(), - maxLength: 2, - ), - ), - ], - ), - - const SizedBox(height: 32), - - // --- TASTO SALVA --- - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: () { - if (nomeController.text.isEmpty) return; - - final storeData = StoreModel( - id: store?.id, // Se nullo, Supabase ne crea uno nuovo - nome: nomeController.text, - indirizzo: indirizzoController.text, - cap: capController.text, - comune: comuneController.text, - provincia: provinciaController.text, - companyId: context - .read() - .state - .company! - .id, // Recuperiamo la companyId - isActive: store?.isActive ?? true, - isPaid: store?.isPaid ?? false, - paymentExpiration: store?.paymentExpiration, - ); - - // Chiamata al Bloc per il salvataggio - context.read().createStore(storeData); - - Navigator.pop(context); - }, - child: Text(store == null ? "CREA NEGOZIO" : "AGGIORNA DATI"), - ), - ), - ], - ), - ), - ), + builder: (context) => StoreForm(store: store), ); } } diff --git a/lib/main.dart b/lib/main.dart index e45f98b..b8b8cb6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -105,7 +105,8 @@ class _FluxAppState extends State { create: (_) => ServicesCubit(context.read()), ), BlocProvider( - create: (_) => ProvidersCubit(context.read()), + create: (_) => + ProvidersCubit(context.read())..loadProviders(null), ), ], child: BlocBuilder( -- 2.43.0