From 6cbf0479a1e5d76b9d4b3f91e3908a632ecade7c Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Sun, 26 Apr 2026 16:29:31 +0200 Subject: [PATCH] =?UTF-8?q?urca=20non=20ci=20credo,=20potrebbe=20gi=C3=A0?= =?UTF-8?q?=20funzionare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- lib/core/blocs/session/session_state.dart | 8 +- lib/core/data/core_repository.dart | 8 + lib/core/widgets/flux_text_field.dart | 3 + .../master_data/staff/blocs/staff_cubit.dart | 75 ++++-- .../master_data/staff/blocs/staff_state.dart | 12 +- .../staff/data/staff_repository.dart | 35 ++- .../master_data/staff/ui/staff_screen.dart | 229 +++++++++++++----- .../master_data/store/bloc/store_cubit.dart | 4 +- .../onboarding/blocs/onboarding_cubit.dart | 24 +- 9 files changed, 297 insertions(+), 101 deletions(-) diff --git a/lib/core/blocs/session/session_state.dart b/lib/core/blocs/session/session_state.dart index b215dcb..d3993f7 100644 --- a/lib/core/blocs/session/session_state.dart +++ b/lib/core/blocs/session/session_state.dart @@ -22,7 +22,7 @@ class SessionState extends Equatable { final User? user; // Utente di Supabase Auth final CompanyModel? company; final StoreModel? currentStore; - final StaffMemberModel? currentStaff; + final StaffMemberModel? currentStaffMember; final OnboardingStep onboardingStep; final bool isMobileDevice; @@ -31,7 +31,7 @@ class SessionState extends Equatable { this.user, this.company, this.currentStore, - this.currentStaff, + this.currentStaffMember, this.onboardingStep = OnboardingStep.none, this.isMobileDevice = false, }); @@ -51,7 +51,7 @@ class SessionState extends Equatable { user: user ?? this.user, company: company ?? this.company, currentStore: currentStore ?? this.currentStore, - currentStaff: currentStaff ?? this.currentStaff, + currentStaffMember: currentStaff ?? this.currentStaffMember, onboardingStep: onboardingStep ?? this.onboardingStep, isMobileDevice: isMobileDevice ?? this.isMobileDevice, ); @@ -63,7 +63,7 @@ class SessionState extends Equatable { user, company, currentStore, - currentStaff, + currentStaffMember, onboardingStep, isMobileDevice, ]; diff --git a/lib/core/data/core_repository.dart b/lib/core/data/core_repository.dart index eebec0f..269bc05 100644 --- a/lib/core/data/core_repository.dart +++ b/lib/core/data/core_repository.dart @@ -108,4 +108,12 @@ class CoreRepository { throw Exception('Creazione profilo staff fallita: $e'); } } + + // Assegna un membro a un negozio + Future assignStaffToStore(String staffId, String storeId) async { + await _supabase.from('staff_in_stores').insert({ + 'staff_member_id': staffId, + 'store_id': storeId, + }); + } } diff --git a/lib/core/widgets/flux_text_field.dart b/lib/core/widgets/flux_text_field.dart index a9bafae..f2cbf24 100644 --- a/lib/core/widgets/flux_text_field.dart +++ b/lib/core/widgets/flux_text_field.dart @@ -20,6 +20,7 @@ class FluxTextField extends StatefulWidget { final List? inputFormatters; final TextCapitalization? textCapitalization; final bool? autocorrect; + final bool? enabled; const FluxTextField({ super.key, // Usiamo super.key per Flutter moderno @@ -39,6 +40,7 @@ class FluxTextField extends StatefulWidget { this.inputFormatters, this.textCapitalization, this.autocorrect, + this.enabled = true, }); @override @@ -115,6 +117,7 @@ class _FluxTextFieldState extends State { inputFormatters: widget.inputFormatters, textCapitalization: widget.textCapitalization ?? TextCapitalization.none, + enabled: widget.enabled, ); } } diff --git a/lib/features/master_data/staff/blocs/staff_cubit.dart b/lib/features/master_data/staff/blocs/staff_cubit.dart index 9c37a93..7bd2120 100644 --- a/lib/features/master_data/staff/blocs/staff_cubit.dart +++ b/lib/features/master_data/staff/blocs/staff_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; @@ -16,7 +18,7 @@ class StaffCubit extends Cubit { // Carica tutto lo staff della compagnia Future loadAllStaff() async { - emit(state.copyWith(isLoading: true, error: null)); + emit(state.copyWith(status: StaffStatus.loading, error: null)); try { final staff = await _repository.getStaffMembers( _sessionCubit.state.company!.id!, @@ -27,18 +29,19 @@ class StaffCubit extends Cubit { } emit( state.copyWith( + status: StaffStatus.success, allStaff: staff, - isLoading: false, storesByStaff: storesByStaff, ), ); } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); + emit(state.copyWith(status: StaffStatus.error, error: e.toString())); } } Future> loadStoresByStaff(String staffId) async { try { + emit(state.copyWith(error: null)); return await _repository.getStaffMemberStore(staffId); } catch (e) { emit(state.copyWith(error: e.toString())); @@ -48,6 +51,7 @@ class StaffCubit extends Cubit { // Carica lo staff di uno specifico negozio e aggiorna la mappa Future loadStaffForStore(String storeId) async { + emit(state.copyWith(error: null)); try { final staffInStore = await _repository.getStaffMembersInStore(storeId); final newMap = Map>.from( @@ -56,48 +60,87 @@ class StaffCubit extends Cubit { newMap[storeId] = staffInStore; emit(state.copyWith(staffByStore: newMap)); } catch (e) { - // Qui potresti gestire l'errore silenziosamente per non bloccare tutta l'UI + emit(state.copyWith(status: StaffStatus.error, error: e.toString())); } } // Salva o aggiorna un membro Future saveStaffMember(StaffMemberModel member) async { - emit(state.copyWith(isLoading: true)); + emit(state.copyWith(status: StaffStatus.loading, error: null)); try { await _repository.saveStaffMember(member); await loadAllStaff(); // Ricarichiamo la lista aggiornata } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); + emit(state.copyWith(status: StaffStatus.error, error: e.toString())); + } + } + + Future inviteStaffMember({ + required StaffMemberModel member, + required List selectedStoreIds, + }) async { + emit(state.copyWith(status: StaffStatus.loading, error: null)); + + try { + // 1. Invitiamo il membro e ci facciamo dare l'ID + final newStaffId = await _repository.inviteStaffMember(member); + + // 2. Assegniamo i negozi uno ad uno (usando il metodo che avevi già nel repo!) + if (selectedStoreIds.isNotEmpty) { + final List assignTasks = []; + for (var storeId in selectedStoreIds) { + assignTasks.add(_repository.assignStaffToStore(newStaffId, storeId)); + } + await Future.wait(assignTasks); // In parallelo per la massima velocità + } + + // 3. Ricarichiamo la lista globale così la UI si aggiorna + await loadAllStaff(); + // (Nota: se hai un loadStaffForStore o loadAllStaff, chiamalo qui per rinfrescare lo stato) + + emit(state.copyWith(status: StaffStatus.success)); + } catch (e) { + emit(state.copyWith(status: StaffStatus.error, error: e.toString())); } } // Associa un dipendente a un negozio Future assignMemberToStore(String staffId, String storeId) async { try { - await _repository.assignToStore(staffId, storeId); + await _repository.assignStaffToStore(staffId, storeId); final stuffStores = await loadStoresByStaff(staffId); final Map> storesByStaff = Map.from( state.storesByStaff, ); storesByStaff[staffId] = stuffStores; - emit(state.copyWith(storesByStaff: storesByStaff)); + emit(state.copyWith(storesByStaff: storesByStaff, error: null)); } catch (e) { - emit(state.copyWith(error: "Errore nell'assegnazione: $e")); + emit( + state.copyWith( + status: StaffStatus.error, + 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 _repository.removeStaffFromStore(staffId, storeId); final stuffStores = await loadStoresByStaff(staffId); final Map> storesByStaff = Map.from( state.storesByStaff, ); storesByStaff[staffId] = stuffStores; - emit(state.copyWith(storesByStaff: storesByStaff)); + emit(state.copyWith(storesByStaff: storesByStaff, error: null)); } catch (e) { - emit(state.copyWith(error: "Errore nella rimozione: $e")); + emit( + state.copyWith( + status: StaffStatus.error, + error: "Errore nella rimozione: $e", + ), + ); } } @@ -105,7 +148,7 @@ class StaffCubit extends Cubit { required StaffMemberModel member, required List selectedStoreIds, }) async { - emit(state.copyWith(isLoading: true)); + emit(state.copyWith(status: StaffStatus.loading, error: null)); try { // 1. Salva o aggiorna l'anagrafica (ci serve l'ID) // Se è un nuovo membro, Supabase ci restituirà l'ID generato @@ -120,7 +163,7 @@ class StaffCubit extends Cubit { if (selectedStoreIds.isNotEmpty) { await Future.wait( selectedStoreIds.map( - (storeId) => _repository.assignToStore(staffId, storeId), + (storeId) => _repository.assignStaffToStore(staffId, storeId), ), ); } @@ -128,9 +171,9 @@ class StaffCubit extends Cubit { // 3. Rinfresca i dati await loadAllStaff(); - emit(state.copyWith(isLoading: false)); + emit(state.copyWith(status: StaffStatus.success)); } catch (e) { - emit(state.copyWith(isLoading: false, error: e.toString())); + emit(state.copyWith(status: StaffStatus.error, error: e.toString())); } } } diff --git a/lib/features/master_data/staff/blocs/staff_state.dart b/lib/features/master_data/staff/blocs/staff_state.dart index a8fbfd4..5ef9055 100644 --- a/lib/features/master_data/staff/blocs/staff_state.dart +++ b/lib/features/master_data/staff/blocs/staff_state.dart @@ -1,42 +1,44 @@ part of 'staff_cubit.dart'; +enum StaffStatus { initial, loading, success, error } + class StaffState extends Equatable { + final StaffStatus status; final List allStaff; final Map> storesByStaff; final Map> staffByStore; - final bool isLoading; final String? error; const StaffState({ + this.status = StaffStatus.initial, this.allStaff = const [], this.storesByStaff = const {}, this.staffByStore = const {}, - this.isLoading = false, this.error, }); StaffState copyWith({ + StaffStatus? status, List? allStaff, Map>? storesByStaff, Map>? staffByStore, - bool? isLoading, String? error, }) { return StaffState( + status: status ?? this.status, allStaff: allStaff ?? this.allStaff, storesByStaff: storesByStaff ?? this.storesByStaff, staffByStore: staffByStore ?? this.staffByStore, - isLoading: isLoading ?? this.isLoading, error: error, ); } @override List get props => [ + status, allStaff, storesByStaff, staffByStore, - isLoading, error, ]; } diff --git a/lib/features/master_data/staff/data/staff_repository.dart b/lib/features/master_data/staff/data/staff_repository.dart index 89032c2..ec35c7c 100644 --- a/lib/features/master_data/staff/data/staff_repository.dart +++ b/lib/features/master_data/staff/data/staff_repository.dart @@ -29,6 +29,37 @@ class StaffRepository { return StaffMemberModel.fromMap(response); } + // --- LOGICA DI INVITO (Tramite Edge Function) --- + + Future inviteStaffMember(StaffMemberModel newMember) async { + if (newMember.email == null || newMember.email!.isEmpty) { + throw Exception( + "L'indirizzo email è obbligatorio per invitare un collega.", + ); + } + + try { + final response = await _supabase.functions.invoke( + 'invite_staff', + body: newMember.toMap(), + ); + + if (response.status != 200) { + throw Exception("Errore dal server: ${response.data}"); + } + + // La funzione ci restituisce l'ID fresco di database! + final responseData = response.data as Map; + return responseData['user_id'] as String; + } on FunctionException catch (e) { + throw Exception( + "Errore di comunicazione con il server: ${e.reasonPhrase}", + ); + } catch (e) { + throw Exception("Impossibile invitare il collega: $e"); + } + } + // --- LOGICA DI GIUNZIONE (Staff <-> Store) --- // Recupera i membri assegnati a uno specifico negozio @@ -62,7 +93,7 @@ class StaffRepository { } // Assegna un membro a un negozio - Future assignToStore(String staffId, String storeId) async { + Future assignStaffToStore(String staffId, String storeId) async { await _supabase.from('staff_in_stores').insert({ 'staff_member_id': staffId, 'store_id': storeId, @@ -70,7 +101,7 @@ class StaffRepository { } // Rimuove l'assegnazione - Future removeFromStore(String staffId, String storeId) async { + Future removeStaffFromStore(String staffId, String storeId) async { await _supabase .from('staff_in_stores') .delete() diff --git a/lib/features/master_data/staff/ui/staff_screen.dart b/lib/features/master_data/staff/ui/staff_screen.dart index 86553e8..11021cb 100644 --- a/lib/features/master_data/staff/ui/staff_screen.dart +++ b/lib/features/master_data/staff/ui/staff_screen.dart @@ -28,6 +28,14 @@ class _StaffScreenState extends State { @override Widget build(BuildContext context) { + // 1. Peschiamo chi siamo noi e che poteri abbiamo + final myRole = context + .read() + .state + .currentStaffMember + ?.systemRole; + final canManageStaff = + myRole == SystemRole.admin || myRole == SystemRole.manager; return Scaffold( backgroundColor: context.background, appBar: AppBar( @@ -45,52 +53,66 @@ class _StaffScreenState extends State { ), ], ), - 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); - }, - ); - }, + body: BlocListener( + listener: (context, state) { + if (state.status == StaffStatus.error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.error ?? 'Errore sconosciuto'), + backgroundColor: Colors.red, + ), + ); + } + }, + child: 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(), ), - ), - ], - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => _openStaffForm(context), - label: const Text("Aggiungi"), - icon: const Icon(Icons.person_add_alt_1), + + // --- LISTA PERSONALE --- + Expanded( + child: BlocBuilder( + builder: (context, state) { + final list = _showAllCompanyStaff + ? state.allStaff + : (state.staffByStore[_selectedStoreId] ?? []); + + if (state.status == StaffStatus.loading && 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: canManageStaff + ? FloatingActionButton.extended( + onPressed: () => _openStaffForm(context), + label: const Text("Aggiungi"), + icon: const Icon(Icons.person_add_alt_1), + ) + : null, ); } @@ -117,6 +139,13 @@ class _StaffScreenState extends State { } Widget _buildStaffCard(StaffMemberModel member) { + final myRole = context + .read() + .state + .currentStaffMember + ?.systemRole; + final canManageStaff = + myRole == SystemRole.admin || myRole == SystemRole.manager; return Card( elevation: 0, shape: RoundedRectangleBorder( @@ -145,8 +174,9 @@ class _StaffScreenState extends State { ), ], ), - trailing: const Icon(Icons.edit_note), - onTap: () => _openStaffForm(context, member: member), + trailing: canManageStaff ? const Icon(Icons.edit_note) : null, + onTap: () => + canManageStaff ? _openStaffForm(context, member: member) : null, ), ); } @@ -155,9 +185,10 @@ class _StaffScreenState extends State { final nameController = TextEditingController(text: member?.name); final emailController = TextEditingController(text: member?.email); final phoneController = TextEditingController(text: member?.phoneNumber); + final jobTitleController = TextEditingController(text: member?.jobTitle); - // 1. Inizializziamo la lista temporanea attingendo dallo stato del Cubit - // Usiamo storesByStaff (la mappa che indicizza i negozi per ogni ID dipendente) + // Variabili di stato per il BottomSheet + SystemRole selectedRole = member?.systemRole ?? SystemRole.user; List tempSelectedStores = context .read() @@ -172,7 +203,6 @@ class _StaffScreenState extends State { isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => StatefulBuilder( - // <--- QUESTO è il segreto per le Chip builder: (context, setModalState) { return Container( decoration: BoxDecoration( @@ -194,7 +224,7 @@ class _StaffScreenState extends State { children: [ Text( member == null - ? "Nuovo Collaboratore" + ? "Invita Collaboratore" // Cambiato il titolo per chiarezza! : "Modifica Collaboratore", style: const TextStyle( fontSize: 20, @@ -202,32 +232,76 @@ class _StaffScreenState extends State { ), ), const SizedBox(height: 24), + FluxTextField( controller: nameController, label: "Nome e Cognome", icon: Icons.person, ), const SizedBox(height: 16), + + // Reso visivamente obbligatorio se è un nuovo utente FluxTextField( controller: emailController, - label: "Email", + label: member == null + ? "Email (Obbligatoria per invito)*" + : "Email", icon: Icons.email, + enabled: + member == + null, // UX: Di solito l'email non si cambia dopo l'invito ), const SizedBox(height: 16), + FluxTextField( controller: phoneController, label: "Telefono", icon: Icons.phone, ), + const SizedBox(height: 16), + + // --- NOVITÀ: SCELTA DEL RUOLO E MANSIONE --- + Row( + children: [ + Expanded( + flex: 2, + child: DropdownButtonFormField( + initialValue: selectedRole, + decoration: const InputDecoration( + labelText: "Ruolo di Sistema", + prefixIcon: Icon(Icons.admin_panel_settings), + ), + items: SystemRole.values.map((role) { + return DropdownMenuItem( + value: role, + child: Text(role.name.toUpperCase()), + ); + }).toList(), + onChanged: (val) { + if (val != null) + setModalState(() => selectedRole = val); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 3, + child: FluxTextField( + controller: jobTitleController, + label: "Qualifica (Es. Tecnico)", + icon: Icons.badge, + ), + ), + ], + ), const SizedBox(height: 24), + const Text( "Assegna ai Negozi", style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 12), - // --- SELETTORE NEGOZI (CHIPS) --- - // Qui usiamo il BlocBuilder per i negozi, ma il setModalState per il refresh BlocBuilder( builder: (context, storeState) { if (storeState.status == StoreStatus.loading) { @@ -244,7 +318,6 @@ class _StaffScreenState extends State { label: Text(store.nome), selected: isSelected, onSelected: (selected) { - // IMPORTANTE: setModalState aggiorna l'UI del BottomSheet setModalState(() { if (selected) { tempSelectedStores.add(store.id!); @@ -269,11 +342,26 @@ class _StaffScreenState extends State { height: 50, child: ElevatedButton( onPressed: () { + // Validazione di base per i nuovi inviti + if (member == null && + emailController.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "L'email è obbligatoria per invitare!", + ), + ), + ); + return; + } + final updatedMember = StaffMemberModel( - id: member?.id, - name: nameController.text, - email: emailController.text, - phoneNumber: phoneController.text, + id: member?.id, // Sarà null se è nuovo + name: nameController.text.trim(), + email: emailController.text.trim(), + phoneNumber: phoneController.text.trim(), + jobTitle: jobTitleController.text.trim(), + systemRole: selectedRole, companyId: GetIt.I .get() .state @@ -282,15 +370,28 @@ class _StaffScreenState extends State { userId: GetIt.I.get().state.user!.id, ); - // Chiamiamo il metodo atomico nel Cubit - context.read().saveStaffWithStores( - member: updatedMember, - selectedStoreIds: tempSelectedStores, - ); + // --- IL BIVIO LOGICO MAGICO --- + if (member == null) { + // 1. UTENTE NUOVO -> Chiamiamo la Edge Function + // (Nota: Per i negozi, potresti dover fare una logica a parte nel Cubit + // perché l'ID del database viene generato DOPO che l'Edge Function ha finito) + context.read().inviteStaffMember( + member: updatedMember, + selectedStoreIds: tempSelectedStores, + ); + } else { + // 2. UTENTE ESISTENTE -> Modifica classica + context.read().saveStaffWithStores( + member: updatedMember, + selectedStoreIds: tempSelectedStores, + ); + } Navigator.pop(context); }, - child: const Text("SALVA COLLABORATORE"), + child: Text( + member == null ? "INVIA INVITO" : "SALVA MODIFICHE", + ), ), ), ], diff --git a/lib/features/master_data/store/bloc/store_cubit.dart b/lib/features/master_data/store/bloc/store_cubit.dart index 09d31d0..ea85d01 100644 --- a/lib/features/master_data/store/bloc/store_cubit.dart +++ b/lib/features/master_data/store/bloc/store_cubit.dart @@ -137,7 +137,7 @@ class StoreCubit extends Cubit { Future assignStaffToStore(String storeId, String staffId) async { try { - await _staffRepository.assignToStore(staffId, storeId); + await _staffRepository.assignStaffToStore(staffId, storeId); // Dopo l'assegnazione, potresti voler ricaricare lo staff per quel negozio loadStores(); } catch (e) { @@ -150,7 +150,7 @@ class StoreCubit extends Cubit { // Rimuove un dipendente da un negozio Future removeStaffFromStore(String staffId, String storeId) async { try { - await _staffRepository.removeFromStore(staffId, storeId); + await _staffRepository.removeStaffFromStore(staffId, storeId); loadStores(); } catch (e) { emit( diff --git a/lib/features/onboarding/blocs/onboarding_cubit.dart b/lib/features/onboarding/blocs/onboarding_cubit.dart index 5c7186c..d00cb33 100644 --- a/lib/features/onboarding/blocs/onboarding_cubit.dart +++ b/lib/features/onboarding/blocs/onboarding_cubit.dart @@ -13,11 +13,13 @@ class OnboardingCubit extends Cubit { final SessionCubit _sessionCubit; OnboardingCubit(this._sessionCubit, this._repository) - : super(OnboardingState( - step: _sessionCubit.state.onboardingStep, - companyId: _sessionCubit.state.company?.id, - storeId: _sessionCubit.state.currentStore?.id, - )); + : super( + OnboardingState( + step: _sessionCubit.state.onboardingStep, + companyId: _sessionCubit.state.company?.id, + storeId: _sessionCubit.state.currentStore?.id, + ), + ); // --- STEP 1: REGISTRAZIONE AZIENDA --- Future saveCompany(String companyName) async { @@ -86,11 +88,17 @@ class OnboardingCubit extends Cubit { // PARANOIA MODE: Forziamo i legami e il ruolo di sistema 'admin' final staffToSave = staff.copyWith( companyId: state.companyId!, - userId: _sessionCubit.state.user!.id, // Dall'utente loggato in Supabase - systemRole: SystemRole.admin, // Blindato! + userId: _sessionCubit.state.user!.id, + systemRole: SystemRole.admin, ); - await _repository.createStaffMember(staffToSave); + // 1. Salviamo lo staff e CI FACCIAMO RESTITUIRE IL MODELLO (con l'id generato!) + final savedStaff = await _repository.createStaffMember(staffToSave); + + // 2. LA MAGIA: Colleghiamo il Paziente Zero al Negozio appena creato! + if (state.storeId != null && savedStaff.id != null) { + await _repository.assignStaffToStore(savedStaff.id!, state.storeId!); + } emit(state.copyWith(isLoading: false, step: OnboardingStep.completed));