dashboard_page.dart 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:intl/intl.dart';
  5. import '../../../providers/auth_provider.dart';
  6. import '../../../providers/dashboard_provider.dart';
  7. import '../../../core/responsive.dart';
  8. import '../../widgets/app_drawer.dart';
  9. class DashboardPage extends ConsumerWidget {
  10. const DashboardPage({super.key});
  11. @override
  12. Widget build(BuildContext context, WidgetRef ref) {
  13. final authState = ref.watch(authProvider);
  14. final agente = authState.agente;
  15. final dashboardAsync = agente != null ? ref.watch(dashboardProvider(agente.idAgente)) : null;
  16. final theme = Theme.of(context);
  17. return Scaffold(
  18. appBar: AppBar(title: Text('Benvenuto, ${agente?.ragioneSociale ?? ''}')),
  19. drawer: AppDrawer(
  20. agente: agente,
  21. onDashboard: () => context.go('/dashboard'),
  22. onOrders: () => context.go('/dashboard/orders'),
  23. onCustomers: () => context.go('/dashboard/customers'),
  24. onCatalog: () => context.go('/dashboard/catalog'),
  25. onPromotions: () => context.go('/dashboard/promotions'),
  26. onPayments: () => context.go('/dashboard/payments'),
  27. onStatistics: () => context.go('/dashboard/statistics'),
  28. onSync: () => context.go('/dashboard/sync'),
  29. onLogout: () => ref.read(authProvider.notifier).logout(),
  30. ),
  31. body: dashboardAsync?.when(
  32. data: (data) => _DashboardContent(data: data, theme: theme, context: context),
  33. loading: () => const Center(child: CircularProgressIndicator()),
  34. error: (e, _) => Center(child: Text('Errore: $e')),
  35. ) ?? const Center(child: CircularProgressIndicator()),
  36. );
  37. }
  38. }
  39. class _DashboardContent extends StatelessWidget {
  40. final DashboardData data;
  41. final ThemeData theme;
  42. final BuildContext context;
  43. const _DashboardContent({required this.data, required this.theme, required this.context});
  44. @override
  45. Widget build(BuildContext context) {
  46. return SingleChildScrollView(
  47. padding: ResponsiveBreakpoints.padding(context),
  48. child: Column(
  49. crossAxisAlignment: CrossAxisAlignment.start,
  50. children: [
  51. // Quick action cards
  52. Wrap(
  53. spacing: 12,
  54. runSpacing: 12,
  55. children: [
  56. _ActionCard(
  57. icon: Icons.receipt_long,
  58. label: 'Nuovo Ordine',
  59. color: theme.colorScheme.primary,
  60. onTap: () => this.context.go('/dashboard/orders/new'),
  61. ),
  62. _ActionCard(
  63. icon: Icons.people,
  64. label: 'Clienti',
  65. color: theme.colorScheme.tertiary,
  66. onTap: () => this.context.go('/dashboard/customers'),
  67. ),
  68. _ActionCard(
  69. icon: Icons.inventory_2,
  70. label: 'Catalogo',
  71. color: theme.colorScheme.secondary,
  72. onTap: () => this.context.go('/dashboard/catalog'),
  73. ),
  74. _ActionCard(
  75. icon: Icons.sync,
  76. label: 'Sincronizza',
  77. color: theme.colorScheme.error,
  78. onTap: () => this.context.go('/dashboard/sync'),
  79. ),
  80. ],
  81. ),
  82. const SizedBox(height: 24),
  83. // Stats cards
  84. Wrap(
  85. spacing: 12,
  86. runSpacing: 12,
  87. children: [
  88. _StatCard(label: 'Nuovi Ordini', value: data.newOrdersCount.toString(), icon: Icons.receipt_long, color: theme.colorScheme.primary),
  89. _StatCard(label: 'Ordini in Sospeso', value: data.ordersOnHoldCount.toString(), icon: Icons.pause_circle, color: theme.colorScheme.tertiary),
  90. _StatCard(label: 'Nuovi Clienti', value: data.newCustomersCount.toString(), icon: Icons.person_add, color: theme.colorScheme.secondary),
  91. ],
  92. ),
  93. const SizedBox(height: 24),
  94. // Top items
  95. if (data.topItems.isNotEmpty) ...[
  96. Text('Articoli Più Venduti', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
  97. const SizedBox(height: 8),
  98. Card(
  99. child: Column(
  100. children: data.topItems.map((item) => ListTile(
  101. leading: CircleAvatar(child: Text(item.codArticolo?.substring(0, 2) ?? '??')),
  102. title: Text(item.descrizione ?? ''),
  103. trailing: Text(NumberFormat.currency(locale: 'it', symbol: '€').format(item.totale ?? 0)),
  104. )).toList(),
  105. ),
  106. ),
  107. ],
  108. // Top customers
  109. if (data.topCustomers.isNotEmpty) ...[
  110. const SizedBox(height: 16),
  111. Text('Top Clienti', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
  112. const SizedBox(height: 8),
  113. Card(
  114. child: Column(
  115. children: data.topCustomers.map((c) => ListTile(
  116. leading: CircleAvatar(child: Text(c.codCliente?.substring(0, 2) ?? '??')),
  117. title: Text(c.ragioneSociale ?? ''),
  118. trailing: Text(NumberFormat.currency(locale: 'it', symbol: '€').format(c.totale ?? 0)),
  119. )).toList(),
  120. ),
  121. ),
  122. ],
  123. ],
  124. ),
  125. );
  126. }
  127. }
  128. class _ActionCard extends StatelessWidget {
  129. final IconData icon;
  130. final String label;
  131. final Color color;
  132. final VoidCallback onTap;
  133. const _ActionCard({required this.icon, required this.label, required this.color, required this.onTap});
  134. @override
  135. Widget build(BuildContext context) {
  136. return SizedBox(
  137. width: ResponsiveBreakpoints.isPhone(context) ? (MediaQuery.of(context).size.width - 48) / 2 : 160,
  138. child: Card(
  139. child: InkWell(
  140. borderRadius: BorderRadius.circular(12),
  141. onTap: onTap,
  142. child: Padding(
  143. padding: const EdgeInsets.all(16),
  144. child: Column(
  145. mainAxisSize: MainAxisSize.min,
  146. children: [
  147. Icon(icon, size: 32, color: color),
  148. const SizedBox(height: 8),
  149. Text(label, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge),
  150. ],
  151. ),
  152. ),
  153. ),
  154. ),
  155. );
  156. }
  157. }
  158. class _StatCard extends StatelessWidget {
  159. final String label;
  160. final String value;
  161. final IconData icon;
  162. final Color color;
  163. const _StatCard({required this.label, required this.value, required this.icon, required this.color});
  164. @override
  165. Widget build(BuildContext context) {
  166. return Card(
  167. child: Padding(
  168. padding: const EdgeInsets.all(16),
  169. child: Row(
  170. mainAxisSize: MainAxisSize.min,
  171. children: [
  172. Icon(icon, color: color),
  173. const SizedBox(width: 12),
  174. Column(
  175. crossAxisAlignment: CrossAxisAlignment.start,
  176. children: [
  177. Text(value, style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)),
  178. Text(label, style: Theme.of(context).textTheme.bodySmall),
  179. ],
  180. ),
  181. ],
  182. ),
  183. ),
  184. );
  185. }
  186. }