| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:fl_chart/fl_chart.dart';
- import 'package:intl/intl.dart';
- import '../../../providers/auth_provider.dart';
- import '../../../providers/database_provider.dart';
- import '../../../business/statistics_service.dart';
- class StatisticsPage extends ConsumerStatefulWidget {
- const StatisticsPage({super.key});
- @override
- ConsumerState<StatisticsPage> createState() => _StatisticsPageState();
- }
- class _StatisticsPageState extends ConsumerState<StatisticsPage>
- with SingleTickerProviderStateMixin {
- late TabController _tabCtrl;
- @override
- void initState() {
- super.initState();
- _tabCtrl = TabController(length: 2, vsync: this);
- }
- @override
- void dispose() {
- _tabCtrl.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text('Statistiche'),
- bottom: TabBar(
- controller: _tabCtrl,
- tabs: const [
- Tab(text: 'Andamento Mensile'),
- Tab(text: 'Top Clienti'),
- ],
- ),
- ),
- body: TabBarView(
- controller: _tabCtrl,
- children: [
- _MonthlyChart(),
- _TopClientsChart(),
- ],
- ),
- );
- }
- }
- class _MonthlyChart extends ConsumerWidget {
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final agente = ref.watch(authProvider).agente;
- if (agente == null) return const Center(child: Text('Login richiesto'));
- final statsService = StatisticsService(
- ref.read(statisticheDaoProvider),
- ref.read(articoliDaoProvider),
- ref.read(clientiDaoProvider),
- ref.read(agentiDaoProvider),
- );
- return FutureBuilder(
- future: statsService.getMonthlyTotals(agente.idAgente),
- builder: (_, snap) {
- if (!snap.hasData) return const Center(child: CircularProgressIndicator());
- final data = snap.data!;
- if (data.isEmpty) return const Center(child: Text('Nessun dato statistico'));
- final sortedKeys = data.keys.toList()..sort();
- final maxVal = data.values.reduce((a, b) => a > b ? a : b);
- return Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('Andamento Vendite', style: Theme.of(context).textTheme.titleMedium),
- const SizedBox(height: 16),
- Expanded(
- child: BarChart(
- BarChartData(
- alignment: BarChartAlignment.spaceAround,
- maxY: maxVal * 1.2,
- barGroups: sortedKeys.asMap().entries.map((e) => BarChartGroupData(
- x: e.key,
- barRods: [BarChartRodData(
- toY: data[e.value]!,
- color: Theme.of(context).colorScheme.primary,
- width: 20,
- borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
- )],
- )).toList(),
- titlesData: FlTitlesData(
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- getTitlesWidget: (value, _) {
- final idx = value.toInt();
- if (idx < 0 || idx >= sortedKeys.length) return const SizedBox();
- final label = sortedKeys[idx];
- return Text(label.length >= 7 ? label.substring(5, 7) : label, style: const TextStyle(fontSize: 10));
- },
- ),
- ),
- leftTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- reservedSize: 60,
- getTitlesWidget: (value, _) => Text(
- NumberFormat.compact(locale: 'it').format(value),
- style: const TextStyle(fontSize: 10),
- ),
- ),
- ),
- topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
- rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
- ),
- gridData: FlGridData(show: true, drawVerticalLine: false),
- borderData: FlBorderData(show: false),
- ),
- ),
- ),
- ],
- ),
- );
- },
- );
- }
- }
- class _TopClientsChart extends ConsumerWidget {
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final agente = ref.watch(authProvider).agente;
- if (agente == null) return const Center(child: Text('Login richiesto'));
- final statsService = StatisticsService(
- ref.read(statisticheDaoProvider),
- ref.read(articoliDaoProvider),
- ref.read(clientiDaoProvider),
- ref.read(agentiDaoProvider),
- );
- return FutureBuilder(
- future: statsService.getTotalsByClient(agente.idAgente),
- builder: (_, snap) {
- if (!snap.hasData) return const Center(child: CircularProgressIndicator());
- final data = snap.data!;
- if (data.isEmpty) return const Center(child: Text('Nessun dato'));
- final sorted = data.entries.toList()..sort((a, b) => b.value.compareTo(a.value));
- final top5 = sorted.take(5).toList();
- // final total = top5.fold(0.0, (s, e) => s + e.value);
- return Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('Top 5 Clienti per Fatturato', style: Theme.of(context).textTheme.titleMedium),
- const SizedBox(height: 16),
- Expanded(
- child: PieChart(
- PieChartData(
- sections: top5.asMap().entries.map((e) => PieChartSectionData(
- value: e.value.value,
- title: '${e.key + 1}',
- color: _colors[e.key % _colors.length],
- radius: 60,
- titleStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
- )).toList(),
- sectionsSpace: 2,
- centerSpaceRadius: 40,
- ),
- ),
- ),
- const SizedBox(height: 16),
- ...top5.asMap().entries.map((e) => Padding(
- padding: const EdgeInsets.symmetric(vertical: 2),
- child: Row(
- children: [
- Container(width: 12, height: 12, decoration: BoxDecoration(color: _colors[e.key % _colors.length], shape: BoxShape.circle)),
- const SizedBox(width: 8),
- Text('Cliente #${e.value.key}', style: Theme.of(context).textTheme.bodySmall),
- const Spacer(),
- Text(NumberFormat.currency(locale: 'it', symbol: '€').format(e.value.value),
- style: Theme.of(context).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold)),
- ],
- ),
- )),
- ],
- ),
- );
- },
- );
- }
- }
- const _colors = [
- Colors.blue,
- Colors.red,
- Colors.green,
- Colors.orange,
- Colors.purple,
- Colors.teal,
- Colors.pink,
- Colors.indigo,
- ];
|