|
@@ -14,7 +14,9 @@ class DashboardPage extends ConsumerWidget {
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
|
final authState = ref.watch(authProvider);
|
|
final authState = ref.watch(authProvider);
|
|
|
final agente = authState.agente;
|
|
final agente = authState.agente;
|
|
|
- final dashboardAsync = agente != null ? ref.watch(dashboardProvider(agente.idAgente)) : null;
|
|
|
|
|
|
|
+ final dashboardAsync = agente != null
|
|
|
|
|
+ ? ref.watch(dashboardProvider(agente.idAgente))
|
|
|
|
|
+ : null;
|
|
|
final theme = Theme.of(context);
|
|
final theme = Theme.of(context);
|
|
|
|
|
|
|
|
return Scaffold(
|
|
return Scaffold(
|
|
@@ -31,11 +33,14 @@ class DashboardPage extends ConsumerWidget {
|
|
|
onSync: () => context.go('/dashboard/sync'),
|
|
onSync: () => context.go('/dashboard/sync'),
|
|
|
onLogout: () => ref.read(authProvider.notifier).logout(),
|
|
onLogout: () => ref.read(authProvider.notifier).logout(),
|
|
|
),
|
|
),
|
|
|
- body: dashboardAsync?.when(
|
|
|
|
|
- data: (data) => _DashboardContent(data: data, theme: theme, context: context),
|
|
|
|
|
- loading: () => const Center(child: CircularProgressIndicator()),
|
|
|
|
|
- error: (e, _) => Center(child: Text('Errore: $e')),
|
|
|
|
|
- ) ?? const Center(child: CircularProgressIndicator()),
|
|
|
|
|
|
|
+ body:
|
|
|
|
|
+ dashboardAsync?.when(
|
|
|
|
|
+ data: (data) =>
|
|
|
|
|
+ _DashboardContent(data: data, theme: theme, context: context),
|
|
|
|
|
+ loading: () => const Center(child: CircularProgressIndicator()),
|
|
|
|
|
+ error: (e, _) => Center(child: Text('Errore: $e')),
|
|
|
|
|
+ ) ??
|
|
|
|
|
+ const Center(child: CircularProgressIndicator()),
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -45,7 +50,11 @@ class _DashboardContent extends StatelessWidget {
|
|
|
final ThemeData theme;
|
|
final ThemeData theme;
|
|
|
final BuildContext context;
|
|
final BuildContext context;
|
|
|
|
|
|
|
|
- const _DashboardContent({required this.data, required this.theme, required this.context});
|
|
|
|
|
|
|
+ const _DashboardContent({
|
|
|
|
|
+ required this.data,
|
|
|
|
|
+ required this.theme,
|
|
|
|
|
+ required this.context,
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
@@ -92,24 +101,58 @@ class _DashboardContent extends StatelessWidget {
|
|
|
spacing: 12,
|
|
spacing: 12,
|
|
|
runSpacing: 12,
|
|
runSpacing: 12,
|
|
|
children: [
|
|
children: [
|
|
|
- _StatCard(label: 'Nuovi Ordini', value: data.newOrdersCount.toString(), icon: Icons.receipt_long, color: theme.colorScheme.primary),
|
|
|
|
|
- _StatCard(label: 'Ordini in Sospeso', value: data.ordersOnHoldCount.toString(), icon: Icons.pause_circle, color: theme.colorScheme.tertiary),
|
|
|
|
|
- _StatCard(label: 'Nuovi Clienti', value: data.newCustomersCount.toString(), icon: Icons.person_add, color: theme.colorScheme.secondary),
|
|
|
|
|
|
|
+ _StatCard(
|
|
|
|
|
+ label: 'Ordini',
|
|
|
|
|
+ value: data.newOrdersCount.toString(),
|
|
|
|
|
+ icon: Icons.receipt_long,
|
|
|
|
|
+ color: theme.colorScheme.primary,
|
|
|
|
|
+ onTap: () => context.go('/dashboard/orders'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _StatCard(
|
|
|
|
|
+ label: 'Ordini in Sospeso',
|
|
|
|
|
+ value: data.ordersOnHoldCount.toString(),
|
|
|
|
|
+ icon: Icons.pause_circle,
|
|
|
|
|
+ color: theme.colorScheme.tertiary,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _StatCard(
|
|
|
|
|
+ label: 'Nuovi Clienti',
|
|
|
|
|
+ value: data.newCustomersCount.toString(),
|
|
|
|
|
+ icon: Icons.person_add,
|
|
|
|
|
+ color: theme.colorScheme.secondary,
|
|
|
|
|
+ ),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
const SizedBox(height: 24),
|
|
const SizedBox(height: 24),
|
|
|
|
|
|
|
|
// Top items
|
|
// Top items
|
|
|
if (data.topItems.isNotEmpty) ...[
|
|
if (data.topItems.isNotEmpty) ...[
|
|
|
- Text('Articoli Più Venduti', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
|
|
|
|
|
|
|
+ Text(
|
|
|
|
|
+ 'Articoli Più Venduti',
|
|
|
|
|
+ style: theme.textTheme.titleMedium?.copyWith(
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
const SizedBox(height: 8),
|
|
const SizedBox(height: 8),
|
|
|
Card(
|
|
Card(
|
|
|
child: Column(
|
|
child: Column(
|
|
|
- children: data.topItems.map((item) => ListTile(
|
|
|
|
|
- leading: CircleAvatar(child: Text(item.codArticolo?.substring(0, 2) ?? '??')),
|
|
|
|
|
- title: Text(item.descrizione ?? ''),
|
|
|
|
|
- trailing: Text(NumberFormat.currency(locale: 'it', symbol: '€').format(item.totale ?? 0)),
|
|
|
|
|
- )).toList(),
|
|
|
|
|
|
|
+ children: data.topItems
|
|
|
|
|
+ .map(
|
|
|
|
|
+ (item) => ListTile(
|
|
|
|
|
+ leading: CircleAvatar(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ item.codArticolo?.substring(0, 2) ?? '??',
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ title: Text(item.descrizione ?? ''),
|
|
|
|
|
+ trailing: Text(
|
|
|
|
|
+ NumberFormat.currency(
|
|
|
|
|
+ locale: 'it',
|
|
|
|
|
+ symbol: '€',
|
|
|
|
|
+ ).format(item.totale ?? 0),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ .toList(),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
@@ -117,15 +160,31 @@ class _DashboardContent extends StatelessWidget {
|
|
|
// Top customers
|
|
// Top customers
|
|
|
if (data.topCustomers.isNotEmpty) ...[
|
|
if (data.topCustomers.isNotEmpty) ...[
|
|
|
const SizedBox(height: 16),
|
|
const SizedBox(height: 16),
|
|
|
- Text('Top Clienti', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
|
|
|
|
|
|
|
+ Text(
|
|
|
|
|
+ 'Top Clienti',
|
|
|
|
|
+ style: theme.textTheme.titleMedium?.copyWith(
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
const SizedBox(height: 8),
|
|
const SizedBox(height: 8),
|
|
|
Card(
|
|
Card(
|
|
|
child: Column(
|
|
child: Column(
|
|
|
- children: data.topCustomers.map((c) => ListTile(
|
|
|
|
|
- leading: CircleAvatar(child: Text(c.codCliente?.substring(0, 2) ?? '??')),
|
|
|
|
|
- title: Text(c.ragioneSociale ?? ''),
|
|
|
|
|
- trailing: Text(NumberFormat.currency(locale: 'it', symbol: '€').format(c.totale ?? 0)),
|
|
|
|
|
- )).toList(),
|
|
|
|
|
|
|
+ children: data.topCustomers
|
|
|
|
|
+ .map(
|
|
|
|
|
+ (c) => ListTile(
|
|
|
|
|
+ leading: CircleAvatar(
|
|
|
|
|
+ child: Text(c.codCliente?.substring(0, 2) ?? '??'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ title: Text(c.ragioneSociale ?? ''),
|
|
|
|
|
+ trailing: Text(
|
|
|
|
|
+ NumberFormat.currency(
|
|
|
|
|
+ locale: 'it',
|
|
|
|
|
+ symbol: '€',
|
|
|
|
|
+ ).format(c.totale ?? 0),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ .toList(),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
@@ -141,12 +200,19 @@ class _ActionCard extends StatelessWidget {
|
|
|
final Color color;
|
|
final Color color;
|
|
|
final VoidCallback onTap;
|
|
final VoidCallback onTap;
|
|
|
|
|
|
|
|
- const _ActionCard({required this.icon, required this.label, required this.color, required this.onTap});
|
|
|
|
|
|
|
+ const _ActionCard({
|
|
|
|
|
+ required this.icon,
|
|
|
|
|
+ required this.label,
|
|
|
|
|
+ required this.color,
|
|
|
|
|
+ required this.onTap,
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
return SizedBox(
|
|
return SizedBox(
|
|
|
- width: ResponsiveBreakpoints.isPhone(context) ? (MediaQuery.of(context).size.width - 48) / 2 : 160,
|
|
|
|
|
|
|
+ width: ResponsiveBreakpoints.isPhone(context)
|
|
|
|
|
+ ? (MediaQuery.of(context).size.width - 48) / 2
|
|
|
|
|
+ : 160,
|
|
|
child: Card(
|
|
child: Card(
|
|
|
child: InkWell(
|
|
child: InkWell(
|
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderRadius: BorderRadius.circular(12),
|
|
@@ -158,7 +224,11 @@ class _ActionCard extends StatelessWidget {
|
|
|
children: [
|
|
children: [
|
|
|
Icon(icon, size: 32, color: color),
|
|
Icon(icon, size: 32, color: color),
|
|
|
const SizedBox(height: 8),
|
|
const SizedBox(height: 8),
|
|
|
- Text(label, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge),
|
|
|
|
|
|
|
+ Text(
|
|
|
|
|
+ label,
|
|
|
|
|
+ textAlign: TextAlign.center,
|
|
|
|
|
+ style: Theme.of(context).textTheme.labelLarge,
|
|
|
|
|
+ ),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
@@ -173,27 +243,43 @@ class _StatCard extends StatelessWidget {
|
|
|
final String value;
|
|
final String value;
|
|
|
final IconData icon;
|
|
final IconData icon;
|
|
|
final Color color;
|
|
final Color color;
|
|
|
|
|
+ final VoidCallback? onTap;
|
|
|
|
|
|
|
|
- const _StatCard({required this.label, required this.value, required this.icon, required this.color});
|
|
|
|
|
|
|
+ const _StatCard({
|
|
|
|
|
+ required this.label,
|
|
|
|
|
+ required this.value,
|
|
|
|
|
+ required this.icon,
|
|
|
|
|
+ required this.color,
|
|
|
|
|
+ this.onTap,
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
return Card(
|
|
return Card(
|
|
|
- child: Padding(
|
|
|
|
|
- padding: const EdgeInsets.all(16),
|
|
|
|
|
- child: Row(
|
|
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
|
|
- children: [
|
|
|
|
|
- Icon(icon, color: color),
|
|
|
|
|
- const SizedBox(width: 12),
|
|
|
|
|
- Column(
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
- children: [
|
|
|
|
|
- Text(value, style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)),
|
|
|
|
|
- Text(label, style: Theme.of(context).textTheme.bodySmall),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ child: InkWell(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
|
|
+ onTap: onTap,
|
|
|
|
|
+ child: Padding(
|
|
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Icon(icon, color: color),
|
|
|
|
|
+ const SizedBox(width: 12),
|
|
|
|
|
+ Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ value,
|
|
|
|
|
+ style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ Text(label, style: Theme.of(context).textTheme.bodySmall),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|