302 lines
10 KiB
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;
|
|
}
|
|
}
|
|
}
|