dashboard_page.dart 9.6 KB

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