refinements
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.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'; // Tuo percorso
|
||||
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:get_it/get_it.dart';
|
||||
@@ -17,18 +17,16 @@ class StaffScreen extends StatefulWidget {
|
||||
|
||||
class _StaffScreenState extends State<StaffScreen> {
|
||||
String? _selectedStoreId;
|
||||
bool _showAllCompanyStaff = true; // Partiamo con la vista globale
|
||||
bool _showAllCompanyStaff = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Carichiamo subito tutto
|
||||
context.read<StaffCubit>().loadAllStaff();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 1. Peschiamo chi siamo noi e che poteri abbiamo
|
||||
final myRole = context
|
||||
.read<SessionCubit>()
|
||||
.state
|
||||
@@ -36,12 +34,12 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
?.systemRole;
|
||||
final canManageStaff =
|
||||
myRole == SystemRole.admin || myRole == SystemRole.manager;
|
||||
|
||||
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(
|
||||
@@ -66,7 +64,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
// --- BARRA FILTRO NEGOZIO (Visibile solo se non 'Tutta l'Azienda') ---
|
||||
// BARRA FILTRO NEGOZIO
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: _showAllCompanyStaff ? 0 : 80,
|
||||
@@ -75,7 +73,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
: _buildStoreSelector(),
|
||||
),
|
||||
|
||||
// --- LISTA PERSONALE ---
|
||||
// LISTA PERSONALE
|
||||
Expanded(
|
||||
child: BlocBuilder<StaffCubit, StaffState>(
|
||||
builder: (context, state) {
|
||||
@@ -87,17 +85,14 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (list.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
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);
|
||||
return _buildStaffCard(list[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -118,7 +113,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
|
||||
Widget _buildStoreSelector() {
|
||||
return BlocBuilder<StoreCubit, StoreState>(
|
||||
// Assumendo tu abbia uno StoreCubit
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -146,6 +140,8 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
?.systemRole;
|
||||
final canManageStaff =
|
||||
myRole == SystemRole.admin || myRole == SystemRole.manager;
|
||||
final hasEmail = member.email != null && member.email!.trim().isNotEmpty;
|
||||
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -156,7 +152,10 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: context.accent.withValues(alpha: 0.1),
|
||||
child: Text(member.name[0], style: TextStyle(color: context.accent)),
|
||||
child: Text(
|
||||
member.name[0].toUpperCase(),
|
||||
style: TextStyle(color: context.accent),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
member.name,
|
||||
@@ -165,55 +164,65 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (member.email != null && member.email!.isNotEmpty)
|
||||
Text(member.email!),
|
||||
if (hasEmail) Text(member.email!),
|
||||
Text(
|
||||
member.phoneNumber != null && member.phoneNumber!.isNotEmpty
|
||||
member.phoneNumber != null &&
|
||||
member.phoneNumber!.trim().isNotEmpty
|
||||
? member.phoneNumber!
|
||||
: "Nessun telefono",
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (member.jobTitle != null && member.jobTitle!.isNotEmpty) ...[
|
||||
Text('Qualifica: ${member.jobTitle!}'),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
|
||||
if (canManageStaff) ...[
|
||||
const SizedBox(width: 8),
|
||||
|
||||
if (!member.hasJoined)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text("Re-invia Invito (In Attesa)"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
onPressed: () {
|
||||
// Chiama la funzione di reset password mascherata da invito
|
||||
context.read<StaffCubit>().resetPasswordOrResendInviteLink(
|
||||
member.email!,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.lock_reset),
|
||||
label: const Text("Invia Reset Password"),
|
||||
onPressed: () {
|
||||
// Chiama LA STESSA IDENTICA FUNZIONE!
|
||||
context.read<StaffCubit>().resetPasswordOrResendInviteLink(
|
||||
member.email!,
|
||||
);
|
||||
},
|
||||
if (member.jobTitle != null &&
|
||||
member.jobTitle!.trim().isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Qualifica: ${member.jobTitle!}',
|
||||
style: TextStyle(
|
||||
color: context.accent,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
// MODIFICA UX: Menu a tendina per le azioni (Salva spazio e previene overflow)
|
||||
trailing: canManageStaff && hasEmail
|
||||
? PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: (value) {
|
||||
if (value == 'invite_reset') {
|
||||
context.read<StaffCubit>().resetPasswordOrResendInviteLink(
|
||||
member.email!,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Operazione richiesta, controlla l\'email!',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'invite_reset',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
!member.hasJoined ? Icons.send : Icons.lock_reset,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
!member.hasJoined
|
||||
? "Re-invia Invito"
|
||||
: "Reset Password",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
onTap: () =>
|
||||
canManageStaff ? _openStaffForm(context, member: member) : null,
|
||||
),
|
||||
@@ -226,7 +235,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
final phoneController = TextEditingController(text: member?.phoneNumber);
|
||||
final jobTitleController = TextEditingController(text: member?.jobTitle);
|
||||
|
||||
// Variabili di stato per il BottomSheet
|
||||
SystemRole selectedRole = member?.systemRole ?? SystemRole.user;
|
||||
List<String> tempSelectedStores =
|
||||
context
|
||||
@@ -263,7 +271,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
children: [
|
||||
Text(
|
||||
member == null
|
||||
? "Invita Collaboratore" // Cambiato il titolo per chiarezza!
|
||||
? "Invita Collaboratore"
|
||||
: "Modifica Collaboratore",
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
@@ -279,16 +287,13 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Reso visivamente obbligatorio se è un nuovo utente
|
||||
FluxTextField(
|
||||
controller: emailController,
|
||||
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
|
||||
enabled: member == null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -299,7 +304,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// --- NOVITÀ: SCELTA DEL RUOLO E MANSIONE ---
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -317,9 +321,8 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
if (val != null)
|
||||
setModalState(() => selectedRole = val);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -382,7 +385,6 @@ 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(
|
||||
@@ -396,7 +398,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
}
|
||||
|
||||
final updatedMember = StaffMemberModel(
|
||||
id: member?.id, // Sarà null se è nuovo
|
||||
id: member?.id,
|
||||
name: nameController.text.trim(),
|
||||
email: emailController.text.trim(),
|
||||
phoneNumber: phoneController.text.trim(),
|
||||
@@ -410,17 +412,12 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
userId: GetIt.I.get<SessionCubit>().state.user!.id,
|
||||
);
|
||||
|
||||
// --- 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,
|
||||
@@ -434,32 +431,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
/* const SizedBox(height: 16),
|
||||
if (!member!.hasJoined)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text("Re-invia Invito (In Attesa)"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
onPressed: () {
|
||||
// Chiama la funzione di reset password mascherata da invito
|
||||
context
|
||||
.read<StaffCubit>()
|
||||
.resetPasswordOrResendInviteLink(member.email!);
|
||||
},
|
||||
)
|
||||
else
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.lock_reset),
|
||||
label: const Text("Invia Reset Password"),
|
||||
onPressed: () {
|
||||
// Chiama LA STESSA IDENTICA FUNZIONE!
|
||||
context
|
||||
.read<StaffCubit>()
|
||||
.resetPasswordOrResendInviteLink(member.email!);
|
||||
},
|
||||
), */
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -54,6 +54,8 @@ class _StoreCardState extends State<StoreCard> {
|
||||
),
|
||||
title: Text(
|
||||
widget.store.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
@@ -65,44 +67,43 @@ class _StoreCardState extends State<StoreCard> {
|
||||
// context.read<StoreBloc>().add(ToggleStoreStatus(store.id, val));
|
||||
},
|
||||
),
|
||||
onTap: () => _openStoreForm(context, store: widget.store),
|
||||
),
|
||||
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<StoreCubit, StoreState>(
|
||||
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",
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Mostra quanti dipendenti ci sono (usando lo StaffCubit)
|
||||
BlocBuilder<StoreCubit, StoreState>(
|
||||
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),
|
||||
),
|
||||
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"),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 16),
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.handshake, size: 16),
|
||||
label: Text(
|
||||
"${widget.store.associatedProviders.length} Providers",
|
||||
),
|
||||
onPressed: () =>
|
||||
_manageStoreProviders(widget.store),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user