|
@@ -0,0 +1,269 @@
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
+import 'package:go_router/go_router.dart';
|
|
|
|
|
+import '../../providers/auth_provider.dart';
|
|
|
|
|
+import '../../core/responsive.dart';
|
|
|
|
|
+
|
|
|
|
|
+class PersistentSidebar extends ConsumerWidget {
|
|
|
|
|
+ final VoidCallback? onExportDatabase;
|
|
|
|
|
+
|
|
|
|
|
+ const PersistentSidebar({super.key, this.onExportDatabase});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context, WidgetRef ref) {
|
|
|
|
|
+ final agente = ref.watch(authProvider).agente;
|
|
|
|
|
+ final currentPath = GoRouterState.of(context).matchedLocation;
|
|
|
|
|
+
|
|
|
|
|
+ return Container(
|
|
|
|
|
+ width: ResponsiveBreakpoints.sidebarWidth,
|
|
|
|
|
+ height: double.infinity,
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ color: Theme.of(context).colorScheme.surface,
|
|
|
|
|
+ border: Border(
|
|
|
|
|
+ right: BorderSide(
|
|
|
|
|
+ color: Theme.of(context).colorScheme.outlineVariant,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // Brand
|
|
|
|
|
+ Padding(
|
|
|
|
|
+ padding: const EdgeInsets.fromLTRB(24, 24, 24, 32),
|
|
|
|
|
+ child: Align(
|
|
|
|
|
+ alignment: Alignment.centerLeft,
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ 'PowerAge',
|
|
|
|
|
+ style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
|
|
|
+ color: Theme.of(context).colorScheme.secondary,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ // Agent profile
|
|
|
|
|
+ Padding(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ CircleAvatar(
|
|
|
|
|
+ radius: 24,
|
|
|
|
|
+ backgroundColor:
|
|
|
|
|
+ Theme.of(context).colorScheme.secondaryContainer,
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ agente?.codAgente ?? '??',
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ color:
|
|
|
|
|
+ Theme.of(context).colorScheme.onSecondaryContainer,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(width: 16),
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ agente?.codAgente ?? '',
|
|
|
|
|
+ style:
|
|
|
|
|
+ Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
|
|
+ ),
|
|
|
|
|
+ Text(
|
|
|
|
|
+ agente?.ragioneSociale ?? '',
|
|
|
|
|
+ style: Theme.of(context).textTheme.bodyMedium
|
|
|
|
|
+ ?.copyWith(
|
|
|
|
|
+ color: Theme.of(
|
|
|
|
|
+ context,
|
|
|
|
|
+ ).colorScheme.onSurfaceVariant,
|
|
|
|
|
+ ),
|
|
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 32),
|
|
|
|
|
+
|
|
|
|
|
+ // Nav items
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: ListView(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
|
|
|
+ children: [
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.analytics_outlined,
|
|
|
|
|
+ label: 'Dashboard',
|
|
|
|
|
+ selected: currentPath == '/dashboard',
|
|
|
|
|
+ onTap: () => context.go('/dashboard'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.shopping_cart_outlined,
|
|
|
|
|
+ label: 'Ordini',
|
|
|
|
|
+ selected: currentPath.startsWith('/dashboard/orders'),
|
|
|
|
|
+ onTap: () => context.go('/dashboard/orders'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.groups_outlined,
|
|
|
|
|
+ label: 'Clienti',
|
|
|
|
|
+ selected: currentPath.startsWith('/dashboard/customers'),
|
|
|
|
|
+ onTap: () => context.go('/dashboard/customers'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.menu_book_outlined,
|
|
|
|
|
+ label: 'Catalogo',
|
|
|
|
|
+ selected: currentPath == '/dashboard/catalog',
|
|
|
|
|
+ onTap: () => context.go('/dashboard/catalog'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.local_offer_outlined,
|
|
|
|
|
+ label: 'Promozioni',
|
|
|
|
|
+ selected: currentPath == '/dashboard/promotions',
|
|
|
|
|
+ onTap: () => context.go('/dashboard/promotions'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.event_note_outlined,
|
|
|
|
|
+ label: 'Scadenze',
|
|
|
|
|
+ selected: currentPath == '/dashboard/payments',
|
|
|
|
|
+ onTap: () => context.go('/dashboard/payments'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.bar_chart_outlined,
|
|
|
|
|
+ label: 'Statistiche',
|
|
|
|
|
+ selected: currentPath == '/dashboard/statistics',
|
|
|
|
|
+ onTap: () => context.go('/dashboard/statistics'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.sync_outlined,
|
|
|
|
|
+ label: 'Sincronizzazione',
|
|
|
|
|
+ selected: currentPath.startsWith('/dashboard/sync'),
|
|
|
|
|
+ onTap: () => context.go('/dashboard/sync'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ // Bottom actions
|
|
|
|
|
+ Container(
|
|
|
|
|
+ padding: const EdgeInsets.all(8),
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ border: Border(
|
|
|
|
|
+ top: BorderSide(
|
|
|
|
|
+ color: Theme.of(
|
|
|
|
|
+ context,
|
|
|
|
|
+ ).colorScheme.outlineVariant.withAlpha(80),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.download_outlined,
|
|
|
|
|
+ label: 'Esporta database',
|
|
|
|
|
+ onTap: onExportDatabase,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.person_search_outlined,
|
|
|
|
|
+ label: 'Cambia agente',
|
|
|
|
|
+ onTap: () => context.go('/dashboard/switch-agent'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _NavItem(
|
|
|
|
|
+ icon: Icons.logout,
|
|
|
|
|
+ label: 'Logout',
|
|
|
|
|
+ iconColor: Theme.of(context).colorScheme.error,
|
|
|
|
|
+ textColor: Theme.of(context).colorScheme.error,
|
|
|
|
|
+ onTap: () =>
|
|
|
|
|
+ ref.read(authProvider.notifier).logout(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ Padding(
|
|
|
|
|
+ padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
|
|
|
|
|
+ child: Align(
|
|
|
|
|
+ alignment: Alignment.centerLeft,
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ 'v2.1.0',
|
|
|
|
|
+ style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
|
|
|
+ color: Theme.of(context).colorScheme.outline,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _NavItem extends StatelessWidget {
|
|
|
|
|
+ final IconData icon;
|
|
|
|
|
+ final String label;
|
|
|
|
|
+ final bool selected;
|
|
|
|
|
+ final VoidCallback? onTap;
|
|
|
|
|
+ final Color? iconColor;
|
|
|
|
|
+ final Color? textColor;
|
|
|
|
|
+
|
|
|
|
|
+ const _NavItem({
|
|
|
|
|
+ required this.icon,
|
|
|
|
|
+ required this.label,
|
|
|
|
|
+ this.selected = false,
|
|
|
|
|
+ this.onTap,
|
|
|
|
|
+ this.iconColor,
|
|
|
|
|
+ this.textColor,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ final effectiveIconColor =
|
|
|
|
|
+ iconColor ??
|
|
|
|
|
+ (selected
|
|
|
|
|
+ ? Theme.of(context).colorScheme.onSecondaryContainer
|
|
|
|
|
+ : Theme.of(context).colorScheme.onSurfaceVariant);
|
|
|
|
|
+ final effectiveTextColor =
|
|
|
|
|
+ textColor ??
|
|
|
|
|
+ (selected
|
|
|
|
|
+ ? Theme.of(context).colorScheme.onSecondaryContainer
|
|
|
|
|
+ : Theme.of(context).colorScheme.onSurfaceVariant);
|
|
|
|
|
+
|
|
|
|
|
+ return Padding(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
|
|
|
|
+ child: Material(
|
|
|
|
|
+ color: selected
|
|
|
|
|
+ ? Theme.of(context).colorScheme.surfaceContainerHighest
|
|
|
|
|
+ : Colors.transparent,
|
|
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
|
|
+ child: InkWell(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
|
|
+ onTap: onTap,
|
|
|
|
|
+ child: Padding(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Icon(icon, size: 22, color: effectiveIconColor),
|
|
|
|
|
+ const SizedBox(width: 16),
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ label,
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ color: effectiveTextColor,
|
|
|
|
|
+ fontWeight: selected ? FontWeight.bold : FontWeight.w500,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|