diff --git a/lib/features/anagrafiche/master_data_hub_content.dart b/lib/features/anagrafiche/master_data_hub_content.dart deleted file mode 100644 index 97bc343..0000000 --- a/lib/features/anagrafiche/master_data_hub_content.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flux/core/theme/theme.dart'; -import 'package:flux/features/customers/ui/customers_content.dart'; -import 'package:flux/features/products/ui/products_screen.dart'; -import 'package:flux/features/staff/ui/staff_screen.dart'; -import 'package:flux/features/store/ui/stores_screen.dart'; - -class MasterDataHubContent extends StatelessWidget { - final Function(Widget) onOpenPage; - - const MasterDataHubContent({super.key, required this.onOpenPage}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Anagrafiche", - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: context.accent, - ), - ), - const SizedBox(height: 8), - Text( - "Gestisci i dati fondamentali del tuo business", - style: TextStyle(color: context.secondaryText), - ), - const SizedBox(height: 32), - - Expanded( - child: GridView.count( - crossAxisCount: MediaQuery.of(context).size.width > 600 ? 3 : 2, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - children: [ - _HubCard( - label: 'Prodotti', - icon: Icons.inventory_2_outlined, - color: Colors.blue, - onTap: () => onOpenPage( - const ProductsScreen(), - ), // Apre ProductsScreen, // Indice per ProductsScreen - ), - _HubCard( - label: 'Clienti', - icon: Icons.people_outlined, - color: Colors.orange, - onTap: () => onOpenPage( - const CustomersContent(), - ), // Indice per CustomersContent - ), - _HubCard( - label: 'Commessi', - icon: Icons.badge_outlined, - color: Colors.teal, - onTap: () => onOpenPage(const StaffScreen()), // Coming soon - ), - _HubCard( - label: 'Negozi', - icon: Icons.storefront_outlined, - color: Colors.purple, - onTap: () => onOpenPage(const StoresScreen()), // Coming soon - ), - ], - ), - ), - ], - ), - ); - } -} - -// Widget semplice per le card dell'hub -class _HubCard extends StatelessWidget { - final String label; - final IconData icon; - final Color color; - final VoidCallback onTap; - - const _HubCard({ - required this.label, - required this.icon, - required this.color, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return Card( - elevation: 0, - color: context.background, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide(color: context.accent.withValues(alpha: 0.1)), - ), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: color, size: 40), - const SizedBox(height: 12), - Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), - ], - ), - ), - ); - } -} diff --git a/lib/features/master_data/staff/ui/staff_screen.dart b/lib/features/master_data/staff/ui/staff_screen.dart index 8684f2a..a8c0a93 100644 --- a/lib/features/master_data/staff/ui/staff_screen.dart +++ b/lib/features/master_data/staff/ui/staff_screen.dart @@ -5,7 +5,7 @@ import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/widgets/flux_text_field.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; // Tuo percorso import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; -import 'package:flux/features/master_data/store/bloc/store_bloc.dart'; +import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; class StaffScreen extends StatefulWidget { const StaffScreen({super.key}); @@ -94,7 +94,7 @@ class _StaffScreenState extends State { } Widget _buildStoreSelector() { - return BlocBuilder( + return BlocBuilder( // Assumendo tu abbia uno StoreCubit builder: (context, state) { return Padding( @@ -222,7 +222,7 @@ class _StaffScreenState extends State { // --- SELETTORE NEGOZI (CHIPS) --- // Qui usiamo il BlocBuilder per i negozi, ma il setModalState per il refresh - BlocBuilder( + BlocBuilder( builder: (context, storeState) { if (storeState.status == StoreStatus.loading) { return const CircularProgressIndicator(); diff --git a/lib/features/master_data/store/bloc/store_bloc.dart b/lib/features/master_data/store/bloc/store_cubit.dart similarity index 70% rename from lib/features/master_data/store/bloc/store_bloc.dart rename to lib/features/master_data/store/bloc/store_cubit.dart index 0fa8dbd..158f16c 100644 --- a/lib/features/master_data/store/bloc/store_bloc.dart +++ b/lib/features/master_data/store/bloc/store_cubit.dart @@ -5,25 +5,18 @@ import 'package:flux/features/master_data/store/data/store_repository.dart'; import 'package:flux/features/master_data/store/models/store_model.dart'; import 'package:get_it/get_it.dart'; -part 'store_events.dart'; part 'store_state.dart'; -class StoreBloc extends Bloc { +class StoreCubit extends Cubit { final StoreRepository _repository = GetIt.I(); final SessionBloc _sessionBloc; - StoreBloc(this._sessionBloc) : super(const StoreState(stores: [])) { - on(_onCreateStore); - on(_onLoadStores); - } + StoreCubit(this._sessionBloc) : super(const StoreState(stores: [])); - Future _onCreateStore( - CreateStoreRequested event, - Emitter emit, - ) async { + Future createStore(final StoreModel store) async { emit(state.copyWith(status: StoreStatus.loading)); try { - await _repository.createStore(event.store); + await _repository.createStore(store); emit(state.copyWith(status: StoreStatus.success)); } catch (e) { emit( @@ -32,10 +25,7 @@ class StoreBloc extends Bloc { } } - Future _onLoadStores( - LoadStoresRequested event, - Emitter emit, - ) async { + Future loadStores() async { emit(state.copyWith(status: StoreStatus.loading)); try { final stores = await _repository.getStoresByCompany( diff --git a/lib/features/master_data/store/bloc/store_events.dart b/lib/features/master_data/store/bloc/store_events.dart deleted file mode 100644 index bc13f5a..0000000 --- a/lib/features/master_data/store/bloc/store_events.dart +++ /dev/null @@ -1,18 +0,0 @@ -part of 'store_bloc.dart'; - -abstract class StoreEvent extends Equatable { - const StoreEvent(); - - @override - List get props => []; -} - -class CreateStoreRequested extends StoreEvent { - final StoreModel store; - const CreateStoreRequested({required this.store}); - - @override - List get props => [store]; -} - -class LoadStoresRequested extends StoreEvent {} diff --git a/lib/features/master_data/store/bloc/store_state.dart b/lib/features/master_data/store/bloc/store_state.dart index 38a1c01..012129c 100644 --- a/lib/features/master_data/store/bloc/store_state.dart +++ b/lib/features/master_data/store/bloc/store_state.dart @@ -1,4 +1,4 @@ -part of 'store_bloc.dart'; +part of 'store_cubit.dart'; enum StoreStatus { initial, loading, success, failure } diff --git a/lib/features/master_data/store/ui/create_store_screen.dart b/lib/features/master_data/store/ui/create_store_screen.dart index 0526aae..1b3c9e8 100644 --- a/lib/features/master_data/store/ui/create_store_screen.dart +++ b/lib/features/master_data/store/ui/create_store_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/features/master_data/store/bloc/store_bloc.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/core/blocs/session/session_bloc.dart'; import 'package:flux/core/theme/theme.dart'; @@ -76,7 +76,7 @@ class _CreateStoreScreenState extends State { provincia: _provinciaController.text.trim().toUpperCase(), ); - context.read().add(CreateStoreRequested(store: store)); + context.read().createStore(store); } } @@ -84,7 +84,7 @@ class _CreateStoreScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Il tuo primo Negozio')), - body: BlocConsumer( + body: BlocConsumer( listener: (context, state) { if (state.status == StoreStatus.success) { context.read().add(AppStarted()); diff --git a/lib/features/master_data/store/ui/stores_screen.dart b/lib/features/master_data/store/ui/stores_screen.dart index 3e38544..a5a833c 100644 --- a/lib/features/master_data/store/ui/stores_screen.dart +++ b/lib/features/master_data/store/ui/stores_screen.dart @@ -4,7 +4,7 @@ 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/staff/blocs/staff_cubit.dart'; -import 'package:flux/features/master_data/store/bloc/store_bloc.dart'; +import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; import 'package:flux/features/master_data/store/models/store_model.dart'; class StoresScreen extends StatefulWidget { @@ -19,7 +19,7 @@ class _StoresScreenState extends State { void initState() { super.initState(); // Carichiamo i negozi e anche lo staff (per poterlo assegnare) - context.read().add(LoadStoresRequested()); + context.read().loadStores(); context.read().loadAllStaff(); } @@ -27,7 +27,7 @@ class _StoresScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("I Tuoi Negozi")), - body: BlocBuilder( + body: BlocBuilder( builder: (context, state) { if (state.status == StoreStatus.loading) { return const Center(child: CircularProgressIndicator()); @@ -279,9 +279,7 @@ class _StoresScreenState extends State { ); // Chiamata al Bloc per il salvataggio - context.read().add( - CreateStoreRequested(store: storeData), - ); + context.read().createStore(storeData); Navigator.pop(context); }, diff --git a/lib/features/staff/blocs/staff_cubit.dart b/lib/features/staff/blocs/staff_cubit.dart deleted file mode 100644 index c1f27c3..0000000 --- a/lib/features/staff/blocs/staff_cubit.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/blocs/session/session_bloc.dart'; -import 'package:flux/features/staff/data/staff_repository.dart'; -import 'package:flux/features/staff/models/staff_member_model.dart'; -import 'package:get_it/get_it.dart'; - -part 'staff_state.dart'; - -class StaffCubit extends Cubit { - final StaffRepository _repository = GetIt.I.get(); - final SessionBloc _sessionBloc; - - StaffCubit(this._sessionBloc) : super(const StaffState()); - - // Carica tutto lo staff della compagnia - Future loadAllStaff() async { - emit(state.copyWith(isLoading: true, error: null)); - try { - final staff = await _repository.getStaffMembers( - _sessionBloc.state.company!.id, - ); - emit(state.copyWith(allStaff: staff, isLoading: false)); - } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); - } - } - - // Carica lo staff di uno specifico negozio e aggiorna la mappa - Future loadStaffForStore(String storeId) async { - try { - final staffInStore = await _repository.getStaffMembersInStore(storeId); - final newMap = Map>.from( - state.staffByStore, - ); - newMap[storeId] = staffInStore; - emit(state.copyWith(staffByStore: newMap)); - } catch (e) { - // Qui potresti gestire l'errore silenziosamente per non bloccare tutta l'UI - } - } - - // Salva o aggiorna un membro - Future saveStaffMember(StaffMemberModel member) async { - emit(state.copyWith(isLoading: true)); - try { - await _repository.saveStaffMember(member); - await loadAllStaff(); // Ricarichiamo la lista aggiornata - } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); - } - } - - // Associa un dipendente a un negozio - Future assignMemberToStore(String staffId, String storeId) async { - try { - await _repository.assignToStore(staffId, storeId); - await loadStaffForStore(storeId); // Aggiorna solo quel negozio nell'UI - } catch (e) { - emit(state.copyWith(error: "Errore nell'assegnazione: $e")); - } - } - - // Rimuove un dipendente da un negozio - Future removeMemberFromStore(String staffId, String storeId) async { - try { - await _repository.removeFromStore(staffId, storeId); - await loadStaffForStore(storeId); - } catch (e) { - emit(state.copyWith(error: "Errore nella rimozione: $e")); - } - } - - Future saveStaffWithStores({ - required StaffMemberModel member, - required List selectedStoreIds, - }) async { - emit(state.copyWith(isLoading: true)); - try { - // 1. Salva o aggiorna l'anagrafica (ci serve l'ID) - // Se è un nuovo membro, Supabase ci restituirà l'ID generato - final savedMember = await _repository.saveStaffMember(member); - final String staffId = savedMember.id!; - - // 2. Sincronizzazione Negozi - // Per semplicità e pulizia, rimuoviamo le vecchie assegnazioni e inseriamo le nuove - // (Oppure facciamo un confronto tra liste, ma il reset & rewrite è più sicuro qui) - await _repository.clearStoreAssignments(staffId); - - if (selectedStoreIds.isNotEmpty) { - await Future.wait( - selectedStoreIds.map( - (storeId) => _repository.assignToStore(staffId, storeId), - ), - ); - } - - // 3. Rinfresca i dati - await loadAllStaff(); - // Aggiorniamo anche lo stato dei negozi coinvolti - for (var storeId in selectedStoreIds) { - await loadStaffForStore(storeId); - } - - emit(state.copyWith(isLoading: false)); - } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); - } - } -} diff --git a/lib/features/staff/blocs/staff_state.dart b/lib/features/staff/blocs/staff_state.dart deleted file mode 100644 index 079aed1..0000000 --- a/lib/features/staff/blocs/staff_state.dart +++ /dev/null @@ -1,33 +0,0 @@ -part of 'staff_cubit.dart'; - -class StaffState extends Equatable { - final List allStaff; - final Map> - staffByStore; // storeId -> List of staff - final bool isLoading; - final String? error; - - const StaffState({ - this.allStaff = const [], - this.staffByStore = const {}, - this.isLoading = false, - this.error, - }); - - StaffState copyWith({ - List? allStaff, - Map>? staffByStore, - bool? isLoading, - String? error, - }) { - return StaffState( - allStaff: allStaff ?? this.allStaff, - staffByStore: staffByStore ?? this.staffByStore, - isLoading: isLoading ?? this.isLoading, - error: error, - ); - } - - @override - List get props => [allStaff, staffByStore, isLoading, error]; -} diff --git a/lib/features/staff/data/staff_repository.dart b/lib/features/staff/data/staff_repository.dart deleted file mode 100644 index 982e58c..0000000 --- a/lib/features/staff/data/staff_repository.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flux/features/staff/models/staff_member_model.dart'; -import 'package:flux/features/store/models/store_model.dart'; -import 'package:get_it/get_it.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; - -class StaffRepository { - final SupabaseClient _supabase = GetIt.I.get(); - - // --- ANAGRAFICA PURA --- - - // Prende tutto lo staff della Company (per l'Hub Anagrafiche) - Future> getStaffMembers(String companyId) async { - final response = await _supabase - .from('staff_member') - .select() - .eq('company_id', companyId) - .order('name', ascending: true); - - return (response as List).map((s) => StaffMemberModel.fromJson(s)).toList(); - } - - Future saveStaffMember(StaffMemberModel member) async { - final response = await _supabase - .from('staff_member') - .upsert(member.toJson()) - .select() - .single(); - - return StaffMemberModel.fromJson(response); - } - - // --- LOGICA DI GIUNZIONE (Staff <-> Store) --- - - // Recupera i membri assegnati a uno specifico negozio - // Qui facciamo una JOIN per avere i dati del membro partendo dalla tabella di giunzione - Future> getStaffMembersInStore(String storeId) async { - final response = await _supabase - .from('staff_in_stores') - .select( - 'staff_member (*)', - ) // Prende tutti i campi della tabella staff_member collegata - .eq('store_id', storeId); - - return (response as List) - .map((item) => StaffMemberModel.fromJson(item['staff_member'])) - .toList(); - } - - // Recupera i negozi associati ad un specifico membro - // Qui facciamo una JOIN per avere i dati del membro partendo dalla tabella di giunzione - Future> getStoresWithStaffMember( - String staffMemberId, - ) async { - final response = await _supabase - .from('staff_in_stores') - .select( - 'store (*)', - ) // Prende tutti i campi della tabella store collegata - .eq('staff_member_id', staffMemberId); - - return (response as List) - .map((item) => StoreModel.fromJson(item['store'])) - .toList(); - } - - // Assegna un membro a un negozio - Future assignToStore(String staffId, String storeId) async { - await _supabase.from('staff_in_stores').insert({ - 'staff_member_id': staffId, - 'store_id': storeId, - }); - } - - // Rimuove l'assegnazione - Future removeFromStore(String staffId, String storeId) async { - await _supabase - .from('staff_in_stores') - .delete() - .eq('staff_member_id', staffId) - .eq('store_id', storeId); - } - - // Nel StaffRepository - - // Utility per pulire le assegnazioni esistenti prima di riscriverle - Future clearStoreAssignments(String staffId) async { - await _supabase - .from('staff_in_stores') - .delete() - .eq('staff_member_id', staffId); - } -} diff --git a/lib/features/staff/models/staff_member_model.dart b/lib/features/staff/models/staff_member_model.dart deleted file mode 100644 index 425d901..0000000 --- a/lib/features/staff/models/staff_member_model.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; // Assicurati che il percorso sia corretto - -class StaffMemberModel extends Equatable { - final String? id; - final String name; - final String email; - final String phone; - final bool isActive; - final String companyId; - - const StaffMemberModel({ - this.id, - required this.name, - this.email = '', - this.phone = '', - this.isActive = true, - required this.companyId, - }); - - factory StaffMemberModel.fromJson(Map json) { - return StaffMemberModel( - id: json['id'], - // Applichiamo il tuo myFormat per visualizzare i nomi correttamente - name: (json['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'], - ); - } - - Map toJson() { - return { - if (id != null) 'id': id, - 'name': name.toLowerCase().trim(), // Salviamo pulito per le query - 'email': email.toLowerCase().trim(), - 'phone': phone.trim(), - 'is_active': isActive, - 'company_id': companyId, - }; - } - - @override - List get props => [id, name, email, phone, isActive, companyId]; -} diff --git a/lib/features/staff/ui/staff_screen.dart b/lib/features/staff/ui/staff_screen.dart deleted file mode 100644 index dcdb339..0000000 --- a/lib/features/staff/ui/staff_screen.dart +++ /dev/null @@ -1,292 +0,0 @@ -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/staff/blocs/staff_cubit.dart'; // Tuo percorso -import 'package:flux/features/staff/models/staff_member_model.dart'; -import 'package:flux/features/store/bloc/store_bloc.dart'; - -class StaffScreen extends StatefulWidget { - const StaffScreen({super.key}); - - @override - State createState() => _StaffScreenState(); -} - -class _StaffScreenState extends State { - String? _selectedStoreId; - bool _showAllCompanyStaff = false; // Partiamo con la vista globale - - @override - void initState() { - super.initState(); - // Carichiamo subito tutto - _selectedStoreId = context.read().state.selectedStore!.id; - context.read().loadStaffForStore(_selectedStoreId!); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: context.background, - appBar: AppBar( - title: const Text("Anagrafica Personale"), - actions: [ - // Toggle per vista Azienda / Negozio - Padding( - padding: const EdgeInsets.only(right: 16), - child: FilterChip( - label: const Text("Tutta l'Azienda"), - selected: _showAllCompanyStaff, - onSelected: (val) => setState(() => _showAllCompanyStaff = val), - selectedColor: context.accent.withValues(alpha: 0.2), - ), - ), - ], - ), - body: Column( - children: [ - // --- BARRA FILTRO NEGOZIO (Visibile solo se non 'Tutta l'Azienda') --- - AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: _showAllCompanyStaff ? 0 : 80, - child: _showAllCompanyStaff - ? const SizedBox() - : _buildStoreSelector(), - ), - - // --- LISTA PERSONALE --- - Expanded( - child: BlocBuilder( - builder: (context, state) { - final list = _showAllCompanyStaff - ? state.allStaff - : (state.staffByStore[_selectedStoreId] ?? []); - - if (state.isLoading && list.isEmpty) { - return const Center(child: CircularProgressIndicator()); - } - - if (list.isEmpty) { - return _buildEmptyState(); - } - - return ListView.separated( - padding: const EdgeInsets.all(16), - itemCount: list.length, - separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { - final member = list[index]; - return _buildStaffCard(member); - }, - ); - }, - ), - ), - ], - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => _openStaffForm(context), - label: const Text("Aggiungi"), - icon: const Icon(Icons.person_add_alt_1), - ), - ); - } - - Widget _buildStoreSelector() { - return BlocBuilder( - // Assumendo tu abbia uno StoreCubit - builder: (context, state) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: DropdownButtonFormField( - initialValue: _selectedStoreId, - decoration: const InputDecoration(labelText: "Filtra per Negozio"), - items: state.stores - .map((s) => DropdownMenuItem(value: s.id, child: Text(s.nome))) - .toList(), - onChanged: (id) { - setState(() => _selectedStoreId = id); - if (id != null) context.read().loadStaffForStore(id); - }, - ), - ); - }, - ); - } - - Widget _buildStaffCard(StaffMemberModel member) { - return Card( - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide(color: context.accent.withValues(alpha: 0.1)), - ), - child: ListTile( - contentPadding: const EdgeInsets.all(12), - leading: CircleAvatar( - backgroundColor: context.accent.withValues(alpha: 0.1), - child: Text(member.name[0], style: TextStyle(color: context.accent)), - ), - title: Text( - member.name, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (member.email.isNotEmpty) Text(member.email), - Text(member.phone.isNotEmpty ? member.phone : "Nessun telefono"), - ], - ), - trailing: const Icon(Icons.edit_note), - onTap: () => _openStaffForm(context, member: member), - ), - ); - } - - void _openStaffForm(BuildContext context, {StaffMemberModel? member}) { - final nameController = TextEditingController(text: member?.name); - final emailController = TextEditingController(text: member?.email); - final phoneController = TextEditingController(text: member?.phone); - - // Lista temporanea per le chip (se è un edit, andrebbe popolata con le assegnazioni attuali) - List tempSelectedStores = []; - - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => StatefulBuilder( - // Necessario per le chip dentro il BottomSheet - builder: (context, setModalState) => 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( - member == null - ? "Nuovo Collaboratore" - : "Modifica Collaboratore", - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 24), - FluxTextField( - controller: nameController, - label: "Nome e Cognome", - icon: Icons.person, - ), - const SizedBox(height: 16), - FluxTextField( - controller: emailController, - label: "Email", - icon: Icons.email, - ), - const SizedBox(height: 16), - FluxTextField( - controller: phoneController, - label: "Telefono", - icon: Icons.phone, - ), - const SizedBox(height: 24), - - const Text( - "Assegna ai Negozi", - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 12), - - // --- SELETTORE NEGOZI (CHIPS) --- - BlocBuilder( - builder: (context, state) { - return Wrap( - spacing: 8, - runSpacing: 8, - children: state.stores.map((store) { - final isSelected = tempSelectedStores.contains( - store.id, - ); - return FilterChip( - label: Text(store.nome), - selected: isSelected, - onSelected: (selected) { - setModalState(() { - selected - ? tempSelectedStores.add(store.id!) - : tempSelectedStores.remove(store.id); - }); - }, - selectedColor: context.accent.withValues(alpha: 0.2), - checkmarkColor: context.accent, - ); - }).toList(), - ); - }, - ), - - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: () { - final newMember = StaffMemberModel( - id: member?.id, - name: nameController.text, - email: emailController.text, - phone: phoneController.text, - companyId: context - .read() - .state - .company! - .id, - ); - - context.read().saveStaffWithStores( - member: newMember, - selectedStoreIds: tempSelectedStores, - ); - Navigator.pop(context); - }, - child: const Text("SALVA COLLABORATORE"), - ), - ), - ], - ), - ), - ), - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.people_outline, size: 64, color: context.secondaryText), - const SizedBox(height: 16), - Text( - "Nessun membro trovato", - style: TextStyle(color: context.secondaryText), - ), - ], - ), - ); - } -} diff --git a/lib/features/store/bloc/store_bloc.dart b/lib/features/store/bloc/store_bloc.dart deleted file mode 100644 index 7502eb3..0000000 --- a/lib/features/store/bloc/store_bloc.dart +++ /dev/null @@ -1,68 +0,0 @@ -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/staff/models/staff_member_model.dart'; -import 'package:flux/features/store/data/store_repository.dart'; -import 'package:flux/features/store/models/store_model.dart'; -import 'package:get_it/get_it.dart'; - -part 'store_events.dart'; -part 'store_state.dart'; - -class StoreBloc extends Bloc { - final StoreRepository _repository = GetIt.I(); - final SessionBloc _sessionBloc; - - StoreBloc(this._sessionBloc) - : super(const StoreState(stores: [], staffByStore: {})) { - on(_onCreateStore); - on(_onLoadStores); - } - - Future _onCreateStore( - CreateStoreRequested event, - Emitter emit, - ) async { - emit(state.copyWith(status: StoreStatus.loading)); - try { - await _repository.createStore(event.store); - emit(state.copyWith(status: StoreStatus.success)); - } catch (e) { - emit( - state.copyWith(status: StoreStatus.failure, errorMessage: e.toString()), - ); - } - } - - Future _onLoadStores( - LoadStoresRequested event, - Emitter emit, - ) async { - emit(state.copyWith(status: StoreStatus.loading)); - try { - final stores = await _repository.getStoresByCompany( - _sessionBloc.state.company!.id, - ); - final staffByStore = >{}; - - for (final store in stores) { - final staff = await _repository.getStaffMembersInStore( - _sessionBloc.state.company!.id, - ); - staffByStore[store] = staff; - } - - emit( - state.copyWith( - status: StoreStatus.success, - stores: stores, // Assicurati di avere 'stores' nello StoreState - staffByStore: staffByStore, - ), - ); - } catch (e) { - emit( - state.copyWith(status: StoreStatus.failure, errorMessage: e.toString()), - ); - } - } -} diff --git a/lib/features/store/bloc/store_events.dart b/lib/features/store/bloc/store_events.dart deleted file mode 100644 index bc13f5a..0000000 --- a/lib/features/store/bloc/store_events.dart +++ /dev/null @@ -1,18 +0,0 @@ -part of 'store_bloc.dart'; - -abstract class StoreEvent extends Equatable { - const StoreEvent(); - - @override - List get props => []; -} - -class CreateStoreRequested extends StoreEvent { - final StoreModel store; - const CreateStoreRequested({required this.store}); - - @override - List get props => [store]; -} - -class LoadStoresRequested extends StoreEvent {} diff --git a/lib/features/store/bloc/store_state.dart b/lib/features/store/bloc/store_state.dart deleted file mode 100644 index 3add3ce..0000000 --- a/lib/features/store/bloc/store_state.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of 'store_bloc.dart'; - -enum StoreStatus { initial, loading, success, failure } - -class StoreState extends Equatable { - final StoreStatus status; - final StoreModel? store; - final String? errorMessage; - final List stores; - final Map> staffByStore; - - const StoreState({ - this.status = StoreStatus.initial, - this.store, - this.errorMessage, - required this.stores, - required this.staffByStore, - }); - - StoreState copyWith({ - StoreStatus? status, - StoreModel? store, - String? errorMessage, - List? stores, - Map>? staffByStore, - }) { - return StoreState( - status: status ?? this.status, - store: store ?? this.store, - errorMessage: errorMessage ?? this.errorMessage, - stores: stores ?? this.stores, - staffByStore: staffByStore ?? this.staffByStore, - ); - } - - @override - List get props => [ - status, - store, - errorMessage, - stores, - staffByStore, - ]; -} diff --git a/lib/features/store/data/store_repository.dart b/lib/features/store/data/store_repository.dart deleted file mode 100644 index b4de907..0000000 --- a/lib/features/store/data/store_repository.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flux/features/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'; - -class StoreRepository { - final SupabaseClient _supabase = GetIt.I.get(); - - /// Crea un nuovo negozio associato alla compagnia dell'utente - Future createStore(StoreModel store) async { - try { - final response = await _supabase - .from('store') - .upsert(store.toJson()) - .select() - .single(); - - return StoreModel.fromJson(response); - } on PostgrestException catch (e) { - // Intercettiamo errori specifici del database - throw e.message; - } catch (e) { - throw 'Errore imprevisto durante la creazione del negozio: $e'; - } - } - - /// Recupera tutti i negozi di una determinata compagnia - Future> getStoresByCompany(String companyId) async { - try { - final response = await _supabase - .from('store') - .select() - .eq('company_id', companyId) - .order('created_at'); - - return (response as List) - .map((json) => StoreModel.fromJson(json)) - .toList(); - } catch (e) { - throw 'Errore nel recupero dei negozi'; - } - } - - Future updateStoreStatus(String id, bool isActive) async { - await _supabase.from('store').update({'is_active': isActive}).eq('id', id); - } - - // --- LOGICA DI GIUNZIONE (Staff <-> Store) --- - - // Recupera i membri assegnati a uno specifico negozio - // Qui facciamo una JOIN per avere i dati del membro partendo dalla tabella di giunzione - Future> getStaffMembersInStore(String storeId) async { - final response = await _supabase - .from('staff_in_stores') - .select( - 'staff_member (*)', - ) // Prende tutti i campi della tabella staff_member collegata - .eq('store_id', storeId); - - return (response as List) - .map((item) => StaffMemberModel.fromJson(item['staff_member'])) - .toList(); - } - - // Recupera i negozi associati ad un specifico membro - // Qui facciamo una JOIN per avere i dati del membro partendo dalla tabella di giunzione - Future> getStoresWithStaffMember( - String staffMemberId, - ) async { - final response = await _supabase - .from('staff_in_stores') - .select( - 'store (*)', - ) // Prende tutti i campi della tabella store collegata - .eq('staff_member_id', staffMemberId); - - return (response as List) - .map((item) => StoreModel.fromJson(item['store'])) - .toList(); - } - - // Assegna un negozio a un membro - Future assignToMember(String staffId, String storeId) async { - await _supabase.from('staff_in_stores').insert({ - 'staff_member_id': staffId, - 'store_id': storeId, - }); - } - - // Rimuove l'assegnazione - Future removeFromStore(String staffId, String storeId) async { - await _supabase - .from('staff_in_stores') - .delete() - .eq('staff_member_id', staffId) - .eq('store_id', storeId); - } - - // Utility per pulire le assegnazioni esistenti prima di riscriverle - Future clearStoreAssignments(String staffId) async { - await _supabase - .from('staff_in_stores') - .delete() - .eq('staff_member_id', staffId); - } -} diff --git a/lib/features/store/models/store_model.dart b/lib/features/store/models/store_model.dart deleted file mode 100644 index 680e2f2..0000000 --- a/lib/features/store/models/store_model.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:equatable/equatable.dart'; - -class StoreModel extends Equatable { - final String? id; - final String nome; - final String companyId; - final bool isActive; - final bool isPaid; - final DateTime? paymentExpiration; - final String indirizzo; - final String cap; - final String comune; - final String provincia; - - const StoreModel({ - this.id, - required this.nome, - required this.companyId, - this.isActive = true, - this.isPaid = false, - this.paymentExpiration, - required this.indirizzo, - required this.cap, - required this.comune, - required this.provincia, - }); - - // Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza - @override - List get props => [ - id, - nome, - companyId, - isActive, - isPaid, - paymentExpiration, - indirizzo, - cap, - comune, - provincia, - ]; - - // Il mitico copyWith per creare nuove istanze modificando solo ciò che serve - StoreModel copyWith({ - String? id, - String? nome, - String? companyId, - bool? isActive, - bool? isPaid, - DateTime? paymentExpiration, - String? indirizzo, - String? cap, - String? comune, - String? provincia, - }) { - return StoreModel( - id: id ?? this.id, - nome: nome ?? this.nome, - companyId: companyId ?? this.companyId, - isActive: isActive ?? this.isActive, - isPaid: isPaid ?? this.isPaid, - paymentExpiration: paymentExpiration ?? this.paymentExpiration, - indirizzo: indirizzo ?? this.indirizzo, - cap: cap ?? this.cap, - comune: comune ?? this.comune, - provincia: provincia ?? this.provincia, - ); - } - - factory StoreModel.fromJson(Map json) { - 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']) - : null, - indirizzo: json['indirizzo'], - cap: json['cap'], - comune: json['comune'], - provincia: json['provincia'], - ); - } - - Map toJson() { - return { - if (id != null) 'id': id, - 'nome': nome, - 'company_id': companyId, - 'is_active': isActive, - 'is_paid': isPaid, - if (paymentExpiration != null) - 'payment_expiration': paymentExpiration!.toIso8601String(), - 'indirizzo': indirizzo, - 'cap': cap, - 'comune': comune, - 'provincia': provincia, - }; - } -} diff --git a/lib/features/store/ui/create_store_screen.dart b/lib/features/store/ui/create_store_screen.dart deleted file mode 100644 index c57516a..0000000 --- a/lib/features/store/ui/create_store_screen.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/features/store/bloc/store_bloc.dart'; -import 'package:flux/features/store/models/store_model.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'; - -class CreateStoreScreen extends StatefulWidget { - const CreateStoreScreen({super.key}); - - @override - State createState() => _CreateStoreScreenState(); -} - -class _CreateStoreScreenState extends State { - final _formKey = GlobalKey(); - - final _nomeController = TextEditingController(); - final _indirizzoController = TextEditingController(); - final _capController = TextEditingController(); - final _comuneController = TextEditingController(); - final _provinciaController = TextEditingController(); - - @override - void dispose() { - _nomeController.dispose(); - _indirizzoController.dispose(); - _capController.dispose(); - _comuneController.dispose(); - _provinciaController.dispose(); - super.dispose(); - } - - /// Funzione magica per copiare i dati dall'azienda salvata in GetIt - void _useCompanyAddress() { - final company = context.read().state.company; - if (company != null) { - setState(() { - _indirizzoController.text = company.indirizzo; - _capController.text = company.cap; - _comuneController.text = - company.citta; // Nel DB company è 'citta', store è 'comune' - _provinciaController.text = company.provincia; - // Suggeriamo anche un nome se vuoto - if (_nomeController.text.isEmpty) { - _nomeController.text = '${company.ragioneSociale} - Sede'; - } - }); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Dati indirizzo copiati dalla sede legale'), - ), - ); - } - } - - void _onSave() { - if (_formKey.currentState!.validate()) { - final company = context.read().state.company; - - if (company == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Errore: Azienda non trovata')), - ); - return; - } - - final store = StoreModel( - nome: _nomeController.text.trim(), - companyId: company.id, - indirizzo: _indirizzoController.text.trim(), - cap: _capController.text.trim(), - comune: _comuneController.text.trim(), - provincia: _provinciaController.text.trim().toUpperCase(), - ); - - context.read().add(CreateStoreRequested(store: store)); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Il tuo primo Negozio')), - body: BlocConsumer( - listener: (context, state) { - if (state.status == StoreStatus.success) { - context.read().add(AppStarted()); - } - if (state.status == StoreStatus.failure) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errorMessage ?? 'Errore salvataggio'), - ), - ); - } - }, - builder: (context, state) { - return SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 32), - - // Nome del Negozio (Icona obbligatoria) - FluxTextField( - label: 'Nome del Negozio', - icon: Icons.storefront_rounded, - controller: _nomeController, - ), - - const SizedBox(height: 24), - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _SectionTitle(title: 'LOCALIZZAZIONE'), - TextButton.icon( - onPressed: _useCompanyAddress, - icon: const Icon(Icons.copy_rounded, size: 16), - label: const Text( - 'Copia da Azienda', - style: TextStyle(fontSize: 12), - ), - ), - ], - ), - const SizedBox(height: 12), - - // Indirizzo (Icona obbligatoria) - FluxTextField( - label: 'Indirizzo e n. civico', - icon: Icons.map_outlined, - controller: _indirizzoController, - ), - const SizedBox(height: 16), - - // RIGA TRIPLA: Comune, CAP, PR - Row( - crossAxisAlignment: CrossAxisAlignment - .start, // Allinea in alto in caso di errori - children: [ - Expanded( - flex: 3, - child: FluxTextField( - label: 'Comune', - icon: Icons.location_city_rounded, // Icona aggiunta - controller: _comuneController, - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, - child: FluxTextField( - label: 'CAP', - icon: Icons.post_add_rounded, // Icona aggiunta - controller: _capController, - keyboardType: TextInputType.number, - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, // Aumentato leggermente per ospitare l'icona - child: FluxTextField( - label: 'PR', - icon: Icons.explore_outlined, // Icona aggiunta - controller: _provinciaController, - ), - ), - ], - ), - - const SizedBox(height: 48), - - _buildSubmitButton(context, state), - ], - ), - ), - ), - ); - }, - ), - ); - } - - Widget _buildSubmitButton(BuildContext context, StoreState state) { - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton( - onPressed: state.status == StoreStatus.loading ? null : _onSave, - child: state.status == StoreStatus.loading - ? const CircularProgressIndicator(color: Colors.white) - : const Text('ATTIVA NEGOZIO'), - ), - ); - } - - Widget _buildHeader(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(Icons.add_location_alt_rounded, color: context.accent, size: 48), - const SizedBox(height: 16), - Text( - 'Dove si trova il tuo store?', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - color: context.primaryText, - ), - ), - const SizedBox(height: 8), - Text( - 'Configura il primo punto vendita per iniziare a gestire i tuoi clienti e le operazioni.', - style: TextStyle(color: context.secondaryText, fontSize: 15), - ), - ], - ); - } -} - -class _SectionTitle extends StatelessWidget { - final String title; - const _SectionTitle({required this.title}); - - @override - Widget build(BuildContext context) { - return Text( - title, - style: TextStyle( - color: context.accent, - fontWeight: FontWeight.w800, - letterSpacing: 1.2, - fontSize: 12, - ), - ); - } -} diff --git a/lib/features/store/ui/stores_screen.dart b/lib/features/store/ui/stores_screen.dart deleted file mode 100644 index 2926314..0000000 --- a/lib/features/store/ui/stores_screen.dart +++ /dev/null @@ -1,297 +0,0 @@ -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/staff/blocs/staff_cubit.dart'; -import 'package:flux/features/staff/models/staff_member_model.dart'; -import 'package:flux/features/store/bloc/store_bloc.dart'; -import 'package:flux/features/store/models/store_model.dart'; - -class StoresScreen extends StatefulWidget { - const StoresScreen({super.key}); - - @override - State createState() => _StoresScreenState(); -} - -class _StoresScreenState extends State { - @override - void initState() { - super.initState(); - // Carichiamo i negozi e anche lo staff (per poterlo assegnare) - context.read().add(LoadStoresRequested()); - context.read().loadAllStaff(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text("I Tuoi Negozi")), - body: BlocBuilder( - builder: (context, state) { - if (state.status == StoreStatus.loading) { - return const Center(child: CircularProgressIndicator()); - } - - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: state.stores.length, - itemBuilder: (context, index) { - final store = state.stores[index]; - return _buildStoreCard(store, state.staffByStore); - }, - ); - }, - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => _openStoreForm(context), - label: const Text("Nuovo Negozio"), - icon: const Icon(Icons.store), - ), - ); - } - - Widget _buildStoreCard( - StoreModel store, - Map> map, - ) { - 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) - ActionChip( - avatar: const Icon(Icons.people, size: 16), - label: Text("${map[store]?.length ?? 0} Dipendenti"), - onPressed: () => _manageStoreStaff(store), - ), - TextButton.icon( - onPressed: () => _openStoreForm(context, store: store), - icon: const Icon(Icons.edit, size: 18), - label: const Text("Modifica"), - ), - ], - ), - ), - ], - ), - ); - } - - 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.text, - ), - 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.local_post_office_outlined, - 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.text, - ), - ), - const SizedBox(width: 8), - Expanded( - flex: 2, - child: FluxTextField( - controller: provinciaController, - label: "Prov", - icon: Icons.explore_outlined, - keyboardType: TextInputType.text, - maxLength: 2, - onChanged: (val) => provinciaController.text = val - .toUpperCase(), // Rimpiazzo automatico in maiuscolo - // Qui potresti aggiungere un rimpiazzo automatico in maiuscolo - ), - ), - ], - ), - - 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().add( - CreateStoreRequested(store: storeData), - ); - - Navigator.pop(context); - }, - child: Text(store == null ? "CREA NEGOZIO" : "AGGIORNA DATI"), - ), - ), - ], - ), - ), - ), - ); - } - - void _manageStoreStaff(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("Personale di ${store.nome}", style: context.titleLarge), - const SizedBox(height: 16), - // Lista di TUTTO lo staff dell'azienda con checkbox - ...state.allStaff.map((person) { - final bool isAssigned = - state.staffByStore[store.id!]?.any( - (s) => s.id == person.id, - ) ?? - false; - - return CheckboxListTile( - title: Text(person.name), - value: isAssigned, - onChanged: (selected) { - if (selected == true) { - context.read().assignMemberToStore( - person.id!, - store.id!, - ); - } else { - context.read().removeMemberFromStore( - person.id!, - store.id!, - ); - } - }, - ); - }), - ], - ), - ); - }, - ), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index 9ed3399..fa4d3c4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,7 +14,7 @@ import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; import 'package:flux/features/master_data/products/data/product_repository.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; import 'package:flux/features/master_data/staff/data/staff_repository.dart'; -import 'package:flux/features/master_data/store/bloc/store_bloc.dart'; +import 'package:flux/features/master_data/store/bloc/store_cubit.dart'; import 'package:flux/features/master_data/store/data/store_repository.dart'; import 'package:flux/features/settings/settings.dart'; import 'package:get_it/get_it.dart'; @@ -84,10 +84,8 @@ class _FluxAppState extends State { providers: [ BlocProvider(create: (_) => AuthBloc()), BlocProvider(create: (_) => CompanyBloc()), - BlocProvider( - create: (_) => - StoreBloc(context.read()) - ..add(LoadStoresRequested()), + BlocProvider( + create: (_) => StoreCubit(context.read())..loadStores(), ), BlocProvider(create: (_) => CustomerBloc()), BlocProvider(