urca non ci credo, potrebbe già funzionare
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -28,6 +28,14 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 1. Peschiamo chi siamo noi e che poteri abbiamo
|
||||
final myRole = context
|
||||
.read<SessionCubit>()
|
||||
.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<StaffScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
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<StaffCubit, StaffState>(
|
||||
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<StaffCubit, StaffState>(
|
||||
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<StaffCubit, StaffState>(
|
||||
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<StaffScreen> {
|
||||
}
|
||||
|
||||
Widget _buildStaffCard(StaffMemberModel member) {
|
||||
final myRole = context
|
||||
.read<SessionCubit>()
|
||||
.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<StaffScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
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<StaffScreen> {
|
||||
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<String> tempSelectedStores =
|
||||
context
|
||||
.read<StaffCubit>()
|
||||
@@ -172,7 +203,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
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<StaffScreen> {
|
||||
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<StaffScreen> {
|
||||
),
|
||||
),
|
||||
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<SystemRole>(
|
||||
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<StoreCubit, StoreState>(
|
||||
builder: (context, storeState) {
|
||||
if (storeState.status == StoreStatus.loading) {
|
||||
@@ -244,7 +318,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
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<StaffScreen> {
|
||||
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<SessionCubit>()
|
||||
.state
|
||||
@@ -282,15 +370,28 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
userId: GetIt.I.get<SessionCubit>().state.user!.id,
|
||||
);
|
||||
|
||||
// Chiamiamo il metodo atomico nel Cubit
|
||||
context.read<StaffCubit>().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<StaffCubit>().inviteStaffMember(
|
||||
member: updatedMember,
|
||||
selectedStoreIds: tempSelectedStores,
|
||||
);
|
||||
} else {
|
||||
// 2. UTENTE ESISTENTE -> Modifica classica
|
||||
context.read<StaffCubit>().saveStaffWithStores(
|
||||
member: updatedMember,
|
||||
selectedStoreIds: tempSelectedStores,
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("SALVA COLLABORATORE"),
|
||||
child: Text(
|
||||
member == null ? "INVIA INVITO" : "SALVA MODIFICHE",
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user