dashboard_page.dart 8.7 KB

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