delivery_app/lib/pages/statistics_page.dart

302 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import '../providers/delivery_provider.dart';
import '../models/delivery_history.dart';
class StatisticsPage extends StatefulWidget {
final DeliveryProvider deliveryProvider;
final String routeName;
const StatisticsPage({
super.key,
required this.deliveryProvider,
required this.routeName,
});
@override
State<StatisticsPage> createState() => _StatisticsPageState();
}
class _StatisticsPageState extends State<StatisticsPage> {
List<DailyStats> _stats = [];
int _totalDeliveries = 0;
int _weeklyDeliveries = 0;
bool _loading = true;
@override
void initState() {
super.initState();
_loadStats();
}
Future<void> _loadStats() async {
final stats = await widget.deliveryProvider.getStats(days: 30);
final total = await widget.deliveryProvider.totalDeliveries;
final weekly = await widget.deliveryProvider.weeklyDeliveries;
if (mounted) {
setState(() {
_stats = stats;
_totalDeliveries = total;
_weeklyDeliveries = weekly;
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
final provider = widget.deliveryProvider;
final todayDelivered = provider.deliveredCount;
final totalStops = provider.totalStops;
final newspaperCounts = provider.newspaperCounts;
final totalNewspapers = provider.totalNewspapers;
// Calculate averages
final daysWithDeliveries = _stats.where((s) => s.deliveredCount > 0).length;
final avgPerDay = daysWithDeliveries > 0
? (_stats.fold<int>(0, (sum, s) => sum + s.deliveredCount) / daysWithDeliveries)
: 0.0;
return Scaffold(
appBar: AppBar(
title: Text('Statistics - ${widget.routeName}'),
),
body: _loading
? const Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _loadStats,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// Today's progress
_buildCard(
icon: Icons.today,
title: 'Today',
child: Column(
children: [
const SizedBox(height: 8),
LinearProgressIndicator(
value: totalStops > 0 ? todayDelivered / totalStops : 0,
minHeight: 10,
borderRadius: BorderRadius.circular(5),
),
const SizedBox(height: 8),
Text(
'$todayDelivered / $totalStops stops delivered',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
if (totalStops > 0)
Text(
'${(todayDelivered / totalStops * 100).toStringAsFixed(0)}% complete',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
const SizedBox(height: 12),
// Newspaper counts
_buildCard(
icon: Icons.newspaper,
title: 'Newspapers',
child: Column(
children: [
const SizedBox(height: 8),
Text(
'$totalNewspapers total newspapers',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 8,
alignment: WrapAlignment.center,
children: newspaperCounts.entries.map((e) {
final color = _npColor(e.key);
return Chip(
avatar: CircleAvatar(
backgroundColor: color,
radius: 8,
),
label: Text('${e.key}: ${e.value}'),
);
}).toList(),
),
],
),
),
const SizedBox(height: 12),
// Summary stats
Row(
children: [
Expanded(
child: _buildStatCard(
icon: Icons.calendar_view_week,
label: 'This Week',
value: '$_weeklyDeliveries',
color: Colors.blue,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard(
icon: Icons.all_inclusive,
label: 'Total All Time',
value: '$_totalDeliveries',
color: Colors.green,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatCard(
icon: Icons.analytics,
label: 'Avg/Day',
value: avgPerDay.toStringAsFixed(1),
color: Colors.orange,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard(
icon: Icons.date_range,
label: 'Active Days',
value: '$daysWithDeliveries',
color: Colors.purple,
),
),
],
),
const SizedBox(height: 20),
// Daily history
_buildCard(
icon: Icons.history,
title: 'Delivery History (Last 30 Days)',
child: _stats.isEmpty
? const Padding(
padding: EdgeInsets.all(16),
child: Text(
'No deliveries recorded yet',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
)
: Column(
children: _stats.map((stat) {
final date = DateTime.tryParse(stat.date);
final dateStr = date != null
? '${date.day}/${date.month}/${date.year}'
: stat.date;
final pct = stat.totalCount > 0
? (stat.deliveredCount / stat.totalCount * 100)
.toStringAsFixed(0)
: '0';
return ListTile(
dense: true,
leading: CircleAvatar(
radius: 16,
backgroundColor: stat.deliveredCount == stat.totalCount
? Colors.green
: Colors.orange,
child: Text(
'$pct%',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
title: Text(dateStr),
subtitle: Text(
'${stat.deliveredCount} / ${stat.totalCount} stops',
),
trailing: stat.deliveredCount == stat.totalCount
? const Icon(Icons.check_circle, color: Colors.green)
: null,
);
}).toList(),
),
),
],
),
),
);
}
Widget _buildCard({
required IconData icon,
required String title,
required Widget child,
}) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 20),
const SizedBox(width: 8),
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
],
),
child,
],
),
),
);
}
Widget _buildStatCard({
required IconData icon,
required String label,
required String value,
required Color color,
}) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color),
),
const SizedBox(height: 4),
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 12)),
],
),
),
);
}
Color _npColor(String np) {
switch (np) {
case 'BN':
return Colors.red;
case 'AD':
return Colors.blue;
case 'TEL':
return Colors.orange;
case 'VK':
return Colors.purple;
default:
return Colors.grey;
}
}
}