diff --git a/lib/features/master_data/master_data_hub_content.dart b/lib/features/master_data/master_data_hub_content.dart index a55d2ac..03082d8 100644 --- a/lib/features/master_data/master_data_hub_content.dart +++ b/lib/features/master_data/master_data_hub_content.dart @@ -2,6 +2,7 @@ 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/master_data/products/ui/products_screen.dart'; +import 'package:flux/features/master_data/providers/ui/providers_master_data_screen.dart'; import 'package:flux/features/master_data/staff/ui/staff_screen.dart'; import 'package:flux/features/master_data/store/ui/stores_screen.dart'; @@ -38,34 +39,57 @@ class MasterDataHubContent extends StatelessWidget { mainAxisSpacing: 16, crossAxisSpacing: 16, children: [ - _HubCard( - label: 'Prodotti', + _buildHubCard( + context, + title: 'Prodotti', + subtitle: 'Anagrafica di Marche e Modelli', icon: Icons.inventory_2_outlined, color: Colors.blue, onTap: () => onOpenPage( const ProductsScreen(), ), // Apre ProductsScreen, // Indice per ProductsScreen ), - _HubCard( - label: 'Clienti', + _buildHubCard( + context, + title: 'Clienti', + subtitle: 'Anagrafica dei clienti del tuo business', icon: Icons.people_outlined, color: Colors.orange, onTap: () => onOpenPage( const CustomersContent(), ), // Indice per CustomersContent ), - _HubCard( - label: 'Commessi', + _buildHubCard( + context, + title: 'Addetti', + subtitle: 'Anagrafica del personale e dei collaboratori', icon: Icons.badge_outlined, color: Colors.teal, onTap: () => onOpenPage(const StaffScreen()), ), - _HubCard( - label: 'Negozi', + _buildHubCard( + context, + title: 'Negozi', + subtitle: 'Anagrafica punti vendita della tua azienda', icon: Icons.storefront_outlined, color: Colors.purple, onTap: () => onOpenPage(const StoresScreen()), ), + _buildHubCard( + context, + title: 'Gestione Provider', + subtitle: 'Anagrafica mandati e servizi abilitati', + icon: Icons.handshake_rounded, + color: Colors.indigo, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ProvidersMasterDataScreen(), + ), + ); + }, + ), ], ), ), @@ -75,41 +99,57 @@ class MasterDataHubContent extends StatelessWidget { } } -// 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), +Widget _buildHubCard( + BuildContext context, { + required String title, + required String subtitle, + required IconData icon, + required Color color, + required VoidCallback onTap, +}) { + return Card( + clipBehavior: Clip.antiAlias, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), // Un pelo più arrotondato + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all( + 24.0, + ), // Aumentiamo il padding per dare respiro child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, // CENTRA VERTICALMENTE + crossAxisAlignment: + CrossAxisAlignment.center, // CENTRA ORIZZONTALMENTE children: [ - Icon(icon, color: color, size: 40), - const SizedBox(height: 12), - Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + // Icona con un leggero sfondo circolare opaco per farla risaltare + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), // <--- API moderna + shape: BoxShape.circle, + ), + child: Icon(icon, size: 48, color: color), + ), + const SizedBox(height: 16), + Text( + title, + textAlign: TextAlign.center, // Centra il testo + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text( + subtitle, + textAlign: TextAlign.center, // Centra il sottotitolo + style: TextStyle(fontSize: 13, color: Colors.grey.shade500), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), ], ), ), - ); - } + ), + ); } diff --git a/lib/features/master_data/providers/blocs/provider_cubit.dart b/lib/features/master_data/providers/blocs/provider_cubit.dart index 517ddb1..a51ff6d 100644 --- a/lib/features/master_data/providers/blocs/provider_cubit.dart +++ b/lib/features/master_data/providers/blocs/provider_cubit.dart @@ -108,7 +108,10 @@ class ProvidersCubit extends Cubit { Future saveProvider(ProviderModel provider) async { emit(state.copyWith(isLoading: true)); try { - await _repository.saveProvider(provider); + final providerWithCompanyId = provider.copyWith( + companyId: _sessionBloc.state.company!.id, + ); + await _repository.saveProvider(providerWithCompanyId); // Ricarichiamo la lista per vedere le modifiche await loadProviders(null); } catch (e) { diff --git a/lib/features/master_data/providers/data/provider_repository.dart b/lib/features/master_data/providers/data/provider_repository.dart index 46bb48a..c155133 100644 --- a/lib/features/master_data/providers/data/provider_repository.dart +++ b/lib/features/master_data/providers/data/provider_repository.dart @@ -40,14 +40,29 @@ class ProviderRepository { // Recupera tutti i provider di una company (per la lista generale) Future> fetchAllCompanyProviders(String companyId) async { try { + // La magia è qui: selezioniamo tutto e chiediamo il conteggio (count) + // della tabella pivot providers_in_stores final response = await _supabase .from('provider') - .select() - .eq('company_id', companyId); + .select(''' + *, + providers_in_stores(count) + ''') + .eq('company_id', companyId) + .order('nome'); - return (response as List).map((m) => ProviderModel.fromMap(m)).toList(); + return (response as List).map((m) { + // Estraiamo il conteggio dalla struttura restituita da Supabase + // La risposta per ogni riga sarà tipo: { "id": "...", "providers_in_stores": [{"count": 5}] } + final storesList = m['providers_in_stores'] as List?; + final count = (storesList != null && storesList.isNotEmpty) + ? storesList[0]['count'] as int + : 0; + + return ProviderModel.fromMap(m).copyWith(storesCount: count); + }).toList(); } catch (e) { - throw Exception('Errore fetch provider: $e'); + throw Exception('Errore fetch all providers: $e'); } } diff --git a/lib/features/master_data/providers/models/provider_model.dart b/lib/features/master_data/providers/models/provider_model.dart index 62d60f2..716bdc3 100644 --- a/lib/features/master_data/providers/models/provider_model.dart +++ b/lib/features/master_data/providers/models/provider_model.dart @@ -11,6 +11,7 @@ class ProviderModel extends Equatable { final bool altro; final bool isActive; final String companyId; + final int storesCount; const ProviderModel({ this.id, @@ -23,6 +24,7 @@ class ProviderModel extends Equatable { required this.altro, required this.isActive, required this.companyId, + this.storesCount = 0, // Numero di store associati, default a 0 }); factory ProviderModel.fromMap(Map map) { @@ -37,6 +39,11 @@ class ProviderModel extends Equatable { altro: map['altro'] ?? false, isActive: map['is_active'] ?? true, companyId: map['company_id'], + storesCount: + map['providers_in_stores'] != null && + map['providers_in_stores'].isNotEmpty + ? map['providers_in_stores'][0]['count'] as int + : 0, // Assumiamo che l'API possa restituire questo campo ); } @@ -72,6 +79,7 @@ class ProviderModel extends Equatable { altro, isActive, companyId, + storesCount, ]; ProviderModel copyWith({ @@ -85,6 +93,7 @@ class ProviderModel extends Equatable { bool? altro, bool? isActive, String? companyId, + int? storesCount, }) { return ProviderModel( id: id ?? this.id, @@ -97,6 +106,7 @@ class ProviderModel extends Equatable { altro: altro ?? this.altro, isActive: isActive ?? this.isActive, companyId: companyId ?? this.companyId, + storesCount: storesCount ?? this.storesCount, ); } } diff --git a/lib/features/master_data/providers/ui/providers_master_data_screen.dart b/lib/features/master_data/providers/ui/providers_master_data_screen.dart index 7ea2325..39fdb9a 100644 --- a/lib/features/master_data/providers/ui/providers_master_data_screen.dart +++ b/lib/features/master_data/providers/ui/providers_master_data_screen.dart @@ -30,6 +30,54 @@ class _ProvidersMasterDataScreenState extends State { return const Center(child: CircularProgressIndicator()); } + if (state.allProviders.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Un'icona grande e stilizzata + Icon( + Icons.handshake_outlined, + size: 80, + color: Colors.indigo.withValues(alpha: 0.3), + ), + const SizedBox(height: 24), + const Text( + "Nessun Provider configurato", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + const Text( + "Aggiungi i partner con cui collabori (es. Enel, WindTre, ecc.) per poter gestire i servizi e i mandati nei tuoi negozi.", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 32), + // Un bel bottone centrato per chi non vuole usare il FAB in basso + ElevatedButton.icon( + onPressed: () => _showProviderForm(context, null), + icon: const Icon(Icons.add), + label: const Text("AGGIUNGI IL PRIMO PROVIDER"), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.indigo, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + ), + ), + ], + ), + ), + ); + } + return ListView.separated( itemCount: state.allProviders.length, separatorBuilder: (context, index) => const Divider(height: 1), @@ -49,7 +97,9 @@ class _ProvidersMasterDataScreenState extends State { provider.nome, style: const TextStyle(fontWeight: FontWeight.bold), ), - subtitle: _buildProviderBadges(provider), + subtitle: _buildCardSubtitle( + provider, + ), // Una funzione che costruisce il sottotitolo con i badge trailing: const Icon(Icons.edit_outlined), onTap: () => _showProviderForm(context, provider), ); @@ -64,6 +114,30 @@ class _ProvidersMasterDataScreenState extends State { ); } + Widget _buildCardSubtitle(ProviderModel provider) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildProviderBadges(provider), // I badge che abbiamo fatto prima + const SizedBox(height: 4), + BlocBuilder( + builder: (context, state) { + // Un piccolo testo che indica il numero di store associati + // Nota: Dovrai assicurarti che il Cubit carichi queste info + return Text( + "Disponibile in ${provider.storesCount} negozi", + style: TextStyle( + fontSize: 11, + color: Colors.indigo.withValues(alpha: 0.7), + ), + ); + }, + ), + _buildProviderBadges(provider), + ], + ); + } + // Visualizza i servizi abilitati per quel provider nella lista Widget _buildProviderBadges(ProviderModel p) { return Wrap(