persistent_sidebar.dart 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import '../../providers/auth_provider.dart';
  5. import '../../core/responsive.dart';
  6. class PersistentSidebar extends ConsumerWidget {
  7. final VoidCallback? onExportDatabase;
  8. const PersistentSidebar({super.key, this.onExportDatabase});
  9. @override
  10. Widget build(BuildContext context, WidgetRef ref) {
  11. final agente = ref.watch(authProvider).agente;
  12. final currentPath = GoRouterState.of(context).matchedLocation;
  13. return Container(
  14. width: ResponsiveBreakpoints.sidebarWidth,
  15. height: double.infinity,
  16. decoration: BoxDecoration(
  17. color: Theme.of(context).colorScheme.surface,
  18. border: Border(
  19. right: BorderSide(
  20. color: Theme.of(context).colorScheme.outlineVariant,
  21. ),
  22. ),
  23. ),
  24. child: Column(
  25. children: [
  26. // Brand
  27. Padding(
  28. padding: const EdgeInsets.fromLTRB(24, 24, 24, 32),
  29. child: Align(
  30. alignment: Alignment.centerLeft,
  31. child: Text(
  32. 'PowerAge',
  33. style: Theme.of(context).textTheme.headlineMedium?.copyWith(
  34. color: Theme.of(context).colorScheme.secondary,
  35. fontWeight: FontWeight.bold,
  36. ),
  37. ),
  38. ),
  39. ),
  40. // Agent profile
  41. Padding(
  42. padding: const EdgeInsets.symmetric(horizontal: 24),
  43. child: Row(
  44. children: [
  45. CircleAvatar(
  46. radius: 24,
  47. backgroundColor:
  48. Theme.of(context).colorScheme.secondaryContainer,
  49. child: Text(
  50. agente?.codAgente ?? '??',
  51. style: TextStyle(
  52. color:
  53. Theme.of(context).colorScheme.onSecondaryContainer,
  54. fontWeight: FontWeight.bold,
  55. fontSize: 16,
  56. ),
  57. ),
  58. ),
  59. const SizedBox(width: 16),
  60. Expanded(
  61. child: Column(
  62. crossAxisAlignment: CrossAxisAlignment.start,
  63. children: [
  64. Text(
  65. agente?.codAgente ?? '',
  66. style:
  67. Theme.of(context).textTheme.titleLarge?.copyWith(
  68. fontWeight: FontWeight.bold,
  69. ),
  70. overflow: TextOverflow.ellipsis,
  71. ),
  72. Text(
  73. agente?.ragioneSociale ?? '',
  74. style: Theme.of(context).textTheme.bodyMedium
  75. ?.copyWith(
  76. color: Theme.of(
  77. context,
  78. ).colorScheme.onSurfaceVariant,
  79. ),
  80. overflow: TextOverflow.ellipsis,
  81. ),
  82. ],
  83. ),
  84. ),
  85. ],
  86. ),
  87. ),
  88. const SizedBox(height: 32),
  89. // Nav items
  90. Expanded(
  91. child: ListView(
  92. padding: const EdgeInsets.symmetric(horizontal: 8),
  93. children: [
  94. _NavItem(
  95. icon: Icons.analytics_outlined,
  96. label: 'Dashboard',
  97. selected: currentPath == '/dashboard',
  98. onTap: () => context.go('/dashboard'),
  99. ),
  100. _NavItem(
  101. icon: Icons.shopping_cart_outlined,
  102. label: 'Ordini',
  103. selected: currentPath.startsWith('/dashboard/orders'),
  104. onTap: () => context.go('/dashboard/orders'),
  105. ),
  106. _NavItem(
  107. icon: Icons.groups_outlined,
  108. label: 'Clienti',
  109. selected: currentPath.startsWith('/dashboard/customers'),
  110. onTap: () => context.go('/dashboard/customers'),
  111. ),
  112. _NavItem(
  113. icon: Icons.menu_book_outlined,
  114. label: 'Catalogo',
  115. selected: currentPath == '/dashboard/catalog',
  116. onTap: () => context.go('/dashboard/catalog'),
  117. ),
  118. _NavItem(
  119. icon: Icons.local_offer_outlined,
  120. label: 'Promozioni',
  121. selected: currentPath == '/dashboard/promotions',
  122. onTap: () => context.go('/dashboard/promotions'),
  123. ),
  124. _NavItem(
  125. icon: Icons.event_note_outlined,
  126. label: 'Scadenze',
  127. selected: currentPath == '/dashboard/payments',
  128. onTap: () => context.go('/dashboard/payments'),
  129. ),
  130. _NavItem(
  131. icon: Icons.bar_chart_outlined,
  132. label: 'Statistiche',
  133. selected: currentPath == '/dashboard/statistics',
  134. onTap: () => context.go('/dashboard/statistics'),
  135. ),
  136. _NavItem(
  137. icon: Icons.sync_outlined,
  138. label: 'Sincronizzazione',
  139. selected: currentPath.startsWith('/dashboard/sync'),
  140. onTap: () => context.go('/dashboard/sync'),
  141. ),
  142. ],
  143. ),
  144. ),
  145. // Bottom actions
  146. Container(
  147. padding: const EdgeInsets.all(8),
  148. decoration: BoxDecoration(
  149. border: Border(
  150. top: BorderSide(
  151. color: Theme.of(
  152. context,
  153. ).colorScheme.outlineVariant.withAlpha(80),
  154. ),
  155. ),
  156. ),
  157. child: Column(
  158. children: [
  159. _NavItem(
  160. icon: Icons.download_outlined,
  161. label: 'Esporta database',
  162. onTap: onExportDatabase,
  163. ),
  164. _NavItem(
  165. icon: Icons.person_search_outlined,
  166. label: 'Cambia agente',
  167. onTap: () => context.go('/dashboard/switch-agent'),
  168. ),
  169. _NavItem(
  170. icon: Icons.logout,
  171. label: 'Logout',
  172. iconColor: Theme.of(context).colorScheme.error,
  173. textColor: Theme.of(context).colorScheme.error,
  174. onTap: () =>
  175. ref.read(authProvider.notifier).logout(),
  176. ),
  177. Padding(
  178. padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
  179. child: Align(
  180. alignment: Alignment.centerLeft,
  181. child: Text(
  182. 'v2.1.0',
  183. style: Theme.of(context).textTheme.labelSmall?.copyWith(
  184. color: Theme.of(context).colorScheme.outline,
  185. ),
  186. ),
  187. ),
  188. ),
  189. ],
  190. ),
  191. ),
  192. ],
  193. ),
  194. );
  195. }
  196. }
  197. class _NavItem extends StatelessWidget {
  198. final IconData icon;
  199. final String label;
  200. final bool selected;
  201. final VoidCallback? onTap;
  202. final Color? iconColor;
  203. final Color? textColor;
  204. const _NavItem({
  205. required this.icon,
  206. required this.label,
  207. this.selected = false,
  208. this.onTap,
  209. this.iconColor,
  210. this.textColor,
  211. });
  212. @override
  213. Widget build(BuildContext context) {
  214. final effectiveIconColor =
  215. iconColor ??
  216. (selected
  217. ? Theme.of(context).colorScheme.onSecondaryContainer
  218. : Theme.of(context).colorScheme.onSurfaceVariant);
  219. final effectiveTextColor =
  220. textColor ??
  221. (selected
  222. ? Theme.of(context).colorScheme.onSecondaryContainer
  223. : Theme.of(context).colorScheme.onSurfaceVariant);
  224. return Padding(
  225. padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
  226. child: Material(
  227. color: selected
  228. ? Theme.of(context).colorScheme.surfaceContainerHighest
  229. : Colors.transparent,
  230. borderRadius: BorderRadius.circular(12),
  231. child: InkWell(
  232. borderRadius: BorderRadius.circular(12),
  233. onTap: onTap,
  234. child: Padding(
  235. padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
  236. child: Row(
  237. children: [
  238. Icon(icon, size: 22, color: effectiveIconColor),
  239. const SizedBox(width: 16),
  240. Expanded(
  241. child: Text(
  242. label,
  243. style: TextStyle(
  244. color: effectiveTextColor,
  245. fontWeight: selected ? FontWeight.bold : FontWeight.w500,
  246. fontSize: 14,
  247. ),
  248. ),
  249. ),
  250. ],
  251. ),
  252. ),
  253. ),
  254. ),
  255. );
  256. }
  257. }