providers-in-store #4
@@ -16,17 +16,17 @@ class StaffRepository {
|
||||
.eq('company_id', companyId)
|
||||
.order('name', ascending: true);
|
||||
|
||||
return (response as List).map((s) => StaffMemberModel.fromJson(s)).toList();
|
||||
return (response as List).map((s) => StaffMemberModel.fromMap(s)).toList();
|
||||
}
|
||||
|
||||
Future<StaffMemberModel> saveStaffMember(StaffMemberModel member) async {
|
||||
final response = await _supabase
|
||||
.from('staff_member')
|
||||
.upsert(member.toJson())
|
||||
.upsert(member.toMap())
|
||||
.select()
|
||||
.single();
|
||||
|
||||
return StaffMemberModel.fromJson(response);
|
||||
return StaffMemberModel.fromMap(response);
|
||||
}
|
||||
|
||||
// --- LOGICA DI GIUNZIONE (Staff <-> Store) ---
|
||||
@@ -42,7 +42,7 @@ class StaffRepository {
|
||||
.eq('store_id', storeId);
|
||||
|
||||
return (response as List)
|
||||
.map((item) => StaffMemberModel.fromJson(item['staff_member']))
|
||||
.map((item) => StaffMemberModel.fromMap(item['staff_member']))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,20 +18,20 @@ class StaffMemberModel extends Equatable {
|
||||
required this.companyId,
|
||||
});
|
||||
|
||||
factory StaffMemberModel.fromJson(Map<String, dynamic> json) {
|
||||
factory StaffMemberModel.fromMap(Map<String, dynamic> map) {
|
||||
return StaffMemberModel(
|
||||
id: json['id'],
|
||||
id: map['id'],
|
||||
// Applichiamo il tuo myFormat per visualizzare i nomi correttamente
|
||||
name: (json['name'] as String).myFormat(),
|
||||
name: (map['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'],
|
||||
email: (map['email'] as String? ?? '').toLowerCase().trim(),
|
||||
phone: (map['phone'] as String? ?? '').trim(),
|
||||
isActive: map['is_active'] ?? true,
|
||||
companyId: map['company_id'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
if (id != null) 'id': id,
|
||||
'name': name.toLowerCase().trim(), // Salviamo pulito per le query
|
||||
|
||||
@@ -54,6 +54,58 @@ class StoreCubit extends Cubit<StoreState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assignProviderToStore(String storeId, String providerId) async {
|
||||
try {
|
||||
await _repository.associateProviderToStore(
|
||||
providerId: providerId,
|
||||
storeId: storeId,
|
||||
);
|
||||
// Dopo l'associazione, potresti voler ricaricare i provider per quel negozio
|
||||
final updatedProviders = await _repository.fetchProvidersForStore(
|
||||
storeId,
|
||||
);
|
||||
final newMap = Map<String, List<ProviderModel>>.from(
|
||||
state.providersByStore,
|
||||
);
|
||||
newMap[storeId] = updatedProviders;
|
||||
emit(state.copyWith(providersByStore: newMap));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StoreStatus.failure,
|
||||
errorMessage: "Errore nell'associazione: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeProviderFromStore(
|
||||
String storeId,
|
||||
String providerId,
|
||||
) async {
|
||||
try {
|
||||
await _repository.removeProviderFromStore(
|
||||
providerId: providerId,
|
||||
storeId: storeId,
|
||||
);
|
||||
final updatedProviders = await _repository.fetchProvidersForStore(
|
||||
storeId,
|
||||
);
|
||||
final newMap = Map<String, List<ProviderModel>>.from(
|
||||
state.providersByStore,
|
||||
);
|
||||
newMap[storeId] = updatedProviders;
|
||||
emit(state.copyWith(providersByStore: newMap));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StoreStatus.failure,
|
||||
errorMessage: "Errore nella rimozione: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assignStaffToStore(String storeId, String staffId) async {
|
||||
try {
|
||||
await _staffRepository.assignToStore(staffId, storeId);
|
||||
|
||||
@@ -24,8 +24,16 @@ class StoreRepository {
|
||||
.from('store')
|
||||
.select('''
|
||||
*,
|
||||
providers_count:providers_in_stores(count),
|
||||
staff_members_count:staff_in_stores(count)
|
||||
associated_providers:providers_in_stores (
|
||||
provider (
|
||||
*
|
||||
)
|
||||
)
|
||||
associated_staff:staff_in_stores (
|
||||
staff_member (
|
||||
*
|
||||
)
|
||||
)
|
||||
''')
|
||||
.eq('company_id', companyId)
|
||||
.order('nome');
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
|
||||
class StoreModel extends Equatable {
|
||||
final String? id;
|
||||
@@ -11,9 +13,9 @@ class StoreModel extends Equatable {
|
||||
final String cap;
|
||||
final String comune;
|
||||
final String provincia;
|
||||
final int providersCount; // Numero di provider associati, utile per la lista
|
||||
final int
|
||||
staffMembersCount; // Numero di membri dello staff associati, utile per la lista
|
||||
final List<ProviderModel> associatedProviders; // Provider associati
|
||||
final List<StaffMemberModel>
|
||||
associatedStaffMembers; // Membri dello staff associati
|
||||
|
||||
const StoreModel({
|
||||
this.id,
|
||||
@@ -26,8 +28,8 @@ class StoreModel extends Equatable {
|
||||
required this.cap,
|
||||
required this.comune,
|
||||
required this.provincia,
|
||||
this.providersCount = 0, // Default a 0 se non specificato
|
||||
this.staffMembersCount = 0, // Default a 0 se non specificato
|
||||
this.associatedProviders = const [],
|
||||
this.associatedStaffMembers = const [],
|
||||
});
|
||||
|
||||
// Fondamentale per Equatable: definisce quali proprietà determinano l'uguaglianza
|
||||
@@ -43,8 +45,8 @@ class StoreModel extends Equatable {
|
||||
cap,
|
||||
comune,
|
||||
provincia,
|
||||
providersCount,
|
||||
staffMembersCount,
|
||||
associatedProviders,
|
||||
associatedStaffMembers,
|
||||
];
|
||||
|
||||
// Il mitico copyWith per creare nuove istanze modificando solo ciò che serve
|
||||
@@ -59,8 +61,8 @@ class StoreModel extends Equatable {
|
||||
String? cap,
|
||||
String? comune,
|
||||
String? provincia,
|
||||
int? providersCount,
|
||||
int? staffMembersCount,
|
||||
List<ProviderModel>? associatedProviders,
|
||||
List<StaffMemberModel>? associatedStaffMembers,
|
||||
}) {
|
||||
return StoreModel(
|
||||
id: id ?? this.id,
|
||||
@@ -73,12 +75,36 @@ class StoreModel extends Equatable {
|
||||
cap: cap ?? this.cap,
|
||||
comune: comune ?? this.comune,
|
||||
provincia: provincia ?? this.provincia,
|
||||
providersCount: providersCount ?? this.providersCount,
|
||||
staffMembersCount: staffMembersCount ?? this.staffMembersCount,
|
||||
associatedProviders: associatedProviders ?? this.associatedProviders,
|
||||
associatedStaffMembers:
|
||||
associatedStaffMembers ?? this.associatedStaffMembers,
|
||||
);
|
||||
}
|
||||
|
||||
factory StoreModel.fromMap(Map<String, dynamic> map) {
|
||||
final providersPivotList = map['associated_providers'] as List?;
|
||||
List<ProviderModel> providers = [];
|
||||
if (providersPivotList != null) {
|
||||
providers = providersPivotList
|
||||
.where((item) => item['provider'] != null) // Sicurezza
|
||||
.map(
|
||||
(item) =>
|
||||
ProviderModel.fromMap(item['provider'] as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
final staffPivotList = map['associated_staff'] as List?;
|
||||
List<StaffMemberModel> staffMembers = [];
|
||||
if (staffPivotList != null) {
|
||||
staffMembers = staffPivotList
|
||||
.where((item) => item['staff_member'] != null) // Sicurezza
|
||||
.map(
|
||||
(item) => StaffMemberModel.fromMap(
|
||||
item['staff_member'] as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
return StoreModel(
|
||||
id: map['id'] as String,
|
||||
nome: map['nome'],
|
||||
@@ -92,15 +118,8 @@ class StoreModel extends Equatable {
|
||||
cap: map['cap'],
|
||||
comune: map['comune'],
|
||||
provincia: map['provincia'],
|
||||
providersCount:
|
||||
map['providers_count'] != null && map['providers_count'].isNotEmpty
|
||||
? map['providers_count'][0]['count'] as int
|
||||
: 0,
|
||||
staffMembersCount:
|
||||
map['staff_members_count'] != null &&
|
||||
map['staff_members_count'].isNotEmpty
|
||||
? map['staff_members_count'][0]['count'] as int
|
||||
: 0,
|
||||
associatedProviders: providers,
|
||||
associatedStaffMembers: staffMembers,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_bloc.dart';
|
||||
import 'package:flux/core/theme/theme.dart';
|
||||
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
|
||||
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||
@@ -94,13 +95,26 @@ class _StoresScreenState extends State<StoresScreen> {
|
||||
builder: (context, storeState) {
|
||||
final staffCount =
|
||||
storeState.staffByStore[store.id]?.length ?? 0;
|
||||
return ActionChip(
|
||||
return Row(
|
||||
children: [
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.people, size: 16),
|
||||
label: Text("$staffCount Dipendenti"),
|
||||
onPressed: () => _manageStoreStaff(store),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.handshake, size: 16),
|
||||
label: Text("${store.providersCount} Providers"),
|
||||
onPressed: () {
|
||||
// Potresti voler navigare alla lista dei provider associati a questo store
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
TextButton.icon(
|
||||
onPressed: () => _openStoreForm(context, store: store),
|
||||
icon: const Icon(Icons.edit, size: 18),
|
||||
@@ -171,6 +185,51 @@ class _StoresScreenState extends State<StoresScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _manageStoreProviders(StoreModel store) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => BlocBuilder<ProvidersCubit, ProvidersState>(
|
||||
builder: (context, state) {
|
||||
// Qui dentro hai già tutta la lista dei provider e quelli associati a questo store
|
||||
// Puoi fare una UI simile a quella dello staff, con una checkbox per ogni provider
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Providers di ${store.nome}", style: context.titleLarge),
|
||||
const SizedBox(height: 16),
|
||||
...state.allProviders.map((provider) {
|
||||
final isAssociated = state.associatedIds.contains(
|
||||
provider.id,
|
||||
);
|
||||
return CheckboxListTile(
|
||||
title: Text(provider.nome),
|
||||
value: isAssociated,
|
||||
onChanged: (selected) {
|
||||
if (selected == true) {
|
||||
context.read<ProvidersCubit>().assignProviderToStore(
|
||||
store.id!,
|
||||
provider.id!,
|
||||
);
|
||||
} else {
|
||||
context.read<ProvidersCubit>().removeProviderFromStore(
|
||||
provider.id!,
|
||||
store.id!,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openStoreForm(BuildContext context, {StoreModel? store}) {
|
||||
final nomeController = TextEditingController(text: store?.nome);
|
||||
final indirizzoController = TextEditingController(text: store?.indirizzo);
|
||||
|
||||
Reference in New Issue
Block a user