dashboard_page.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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
  16. ? ref.watch(dashboardProvider(agente.idAgente))
  17. : null;
  18. final theme = Theme.of(context);
  19. return Scaffold(
  20. appBar: AppBar(title: Text('Benvenuto, ${agente?.ragioneSociale ?? ''}')),
  21. drawer: AppDrawer(
  22. agente: agente,
  23. onDashboard: () => context.go('/dashboard'),
  24. onOrders: () => context.go('/dashboard/orders'),
  25. onCustomers: () => context.go('/dashboard/customers'),
  26. onCatalog: () => context.go('/dashboard/catalog'),
  27. onPromotions: () => context.go('/dashboard/promotions'),
  28. onPayments: () => context.go('/dashboard/payments'),
  29. onStatistics: () => context.go('/dashboard/statistics'),
  30. onSync: () => context.go('/dashboard/sync'),
  31. onLogout: () => ref.read(authProvider.notifier).logout(),
  32. ),
  33. body:
  34. dashboardAsync?.when(
  35. data: (data) =>
  36. _DashboardContent(data: data, theme: theme, context: context),
  37. loading: () => const Center(child: CircularProgressIndicator()),
  38. error: (e, _) => Center(child: Text('Errore: $e')),
  39. ) ??
  40. const Center(child: CircularProgressIndicator()),
  41. );
  42. }
  43. }
  44. class _DashboardContent extends StatelessWidget {
  45. final DashboardData data;
  46. final ThemeData theme;
  47. final BuildContext context;
  48. const _DashboardContent({
  49. required this.data,
  50. required this.theme,
  51. required this.context,
  52. });
  53. @override
  54. Widget build(BuildContext context) {
  55. return SingleChildScrollView(
  56. padding: ResponsiveBreakpoints.padding(context),
  57. child: Column(
  58. crossAxisAlignment: CrossAxisAlignment.start,
  59. children: [
  60. // Quick action cards
  61. Wrap(
  62. spacing: 12,
  63. runSpacing: 12,
  64. children: [
  65. _ActionCard(
  66. icon: Icons.receipt_long,
  67. label: 'Nuovo Ordine',
  68. color: theme.colorScheme.primary,
  69. onTap: () => this.context.go('/dashboard/orders/new'),
  70. ),
  71. _ActionCard(
  72. icon: Icons.people,
  73. label: 'Clienti',
  74. color: theme.colorScheme.tertiary,
  75. onTap: () => this.context.go('/dashboard/customers'),
  76. ),
  77. _ActionCard(
  78. icon: Icons.inventory_2,
  79. label: 'Catalogo',
  80. color: theme.colorScheme.secondary,
  81. onTap: () => this.context.go('/dashboard/catalog'),
  82. ),
  83. _ActionCard(
  84. icon: Icons.sync,
  85. label: 'Sincronizza',
  86. color: theme.colorScheme.error,
  87. onTap: () => this.context.go('/dashboard/sync'),
  88. ),
  89. ],
  90. ),
  91. const SizedBox(height: 24),
  92. // Stats cards
  93. Wrap(
  94. spacing: 12,
  95. runSpacing: 12,
  96. children: [
  97. _StatCard(
  98. label: 'Ordini',
  99. value: data.newOrdersCount.toString(),
  100. icon: Icons.receipt_long,
  101. color: theme.colorScheme.primary,
  102. onTap: () => context.go('/dashboard/orders'),
  103. ),
  104. _StatCard(
  105. label: 'Ordini in Sospeso',
  106. value: data.ordersOnHoldCount.toString(),
  107. icon: Icons.pause_circle,
  108. color: theme.colorScheme.tertiary,
  109. ),
  110. _StatCard(
  111. label: 'Nuovi Clienti',
  112. value: data.newCustomersCount.toString(),
  113. icon: Icons.person_add,
  114. color: theme.colorScheme.secondary,
  115. ),
  116. ],
  117. ),
  118. const SizedBox(height: 24),
  119. // Top items
  120. if (data.topItems.isNotEmpty) ...[
  121. Text(
  122. 'Articoli Più Venduti',
  123. style: theme.textTheme.titleMedium?.copyWith(
  124. fontWeight: FontWeight.bold,
  125. ),
  126. ),
  127. const SizedBox(height: 8),
  128. Card(
  129. child: Column(
  130. children: data.topItems
  131. .map(
  132. (item) => ListTile(
  133. leading: CircleAvatar(
  134. child: Text(
  135. item.codArticolo?.substring(0, 2) ?? '??',
  136. ),
  137. ),
  138. title: Text(item.descrizione ?? ''),
  139. trailing: Text(
  140. NumberFormat.currency(
  141. locale: 'it',
  142. symbol: '€',
  143. ).format(item.totale ?? 0),
  144. ),
  145. ),
  146. )
  147. .toList(),
  148. ),
  149. ),
  150. ],
  151. // Top customers
  152. if (data.topCustomers.isNotEmpty) ...[
  153. const SizedBox(height: 16),
  154. Text(
  155. 'Top Clienti',
  156. style: theme.textTheme.titleMedium?.copyWith(
  157. fontWeight: FontWeight.bold,
  158. ),
  159. ),
  160. const SizedBox(height: 8),
  161. Card(
  162. child: Column(
  163. children: data.topCustomers
  164. .map(
  165. (c) => ListTile(
  166. leading: CircleAvatar(
  167. child: Text(c.codCliente?.substring(0, 2) ?? '??'),
  168. ),
  169. title: Text(c.ragioneSociale ?? ''),
  170. trailing: Text(
  171. NumberFormat.currency(
  172. locale: 'it',
  173. symbol: '€',
  174. ).format(c.totale ?? 0),
  175. ),
  176. ),
  177. )
  178. .toList(),
  179. ),
  180. ),
  181. ],
  182. ],
  183. ),
  184. );
  185. }
  186. }
  187. class _ActionCard extends StatelessWidget {
  188. final IconData icon;
  189. final String label;
  190. final Color color;
  191. final VoidCallback onTap;
  192. const _ActionCard({
  193. required this.icon,
  194. required this.label,
  195. required this.color,
  196. required this.onTap,
  197. });
  198. @override
  199. Widget build(BuildContext context) {
  200. return SizedBox(
  201. width: ResponsiveBreakpoints.isPhone(context)
  202. ? (MediaQuery.of(context).size.width - 48) / 2
  203. : 160,
  204. child: Card(
  205. child: InkWell(
  206. borderRadius: BorderRadius.circular(12),
  207. onTap: onTap,
  208. child: Padding(
  209. padding: const EdgeInsets.all(16),
  210. child: Column(
  211. mainAxisSize: MainAxisSize.min,
  212. children: [
  213. Icon(icon, size: 32, color: color),
  214. const SizedBox(height: 8),
  215. Text(
  216. label,
  217. textAlign: TextAlign.center,
  218. style: Theme.of(context).textTheme.labelLarge,
  219. ),
  220. ],
  221. ),
  222. ),
  223. ),
  224. ),
  225. );
  226. }
  227. }
  228. class _StatCard extends StatelessWidget {
  229. final String label;
  230. final String value;
  231. final IconData icon;
  232. final Color color;
  233. final VoidCallback? onTap;
  234. const _StatCard({
  235. required this.label,
  236. required this.value,
  237. required this.icon,
  238. required this.color,
  239. this.onTap,
  240. });
  241. @override
  242. Widget build(BuildContext context) {
  243. return Card(
  244. child: InkWell(
  245. borderRadius: BorderRadius.circular(12),
  246. onTap: onTap,
  247. child: Padding(
  248. padding: const EdgeInsets.all(16),
  249. child: Row(
  250. mainAxisSize: MainAxisSize.min,
  251. children: [
  252. Icon(icon, color: color),
  253. const SizedBox(width: 12),
  254. Column(
  255. crossAxisAlignment: CrossAxisAlignment.start,
  256. children: [
  257. Text(
  258. value,
  259. style: Theme.of(context).textTheme.headlineSmall?.copyWith(
  260. fontWeight: FontWeight.bold,
  261. ),
  262. ),
  263. Text(label, style: Theme.of(context).textTheme.bodySmall),
  264. ],
  265. ),
  266. ],
  267. ),
  268. ),
  269. ),
  270. );
  271. }
  272. }