From f4d59a2f4f8df088c58955000735a8714e4afad5 Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Fri, 17 Apr 2026 14:58:29 +0200 Subject: [PATCH] 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(