import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import '../../core/app_colors.dart'; import '../../database/db_helper.dart'; class AdminStatsScreen extends StatefulWidget { const AdminStatsScreen({super.key}); @override State createState() => _AdminStatsScreenState(); } class _AdminStatsScreenState extends State { Map _stats = {}; List> _byColonia = []; List> _byRoute = []; List> _byWeek = []; bool _loading = true; @override void initState() { super.initState(); _load(); } Future _load() async { final s = await DbHelper.getAdminStats(); final bc = await DbHelper.getReportesByColonia(); final br = await DbHelper.getIncidentesByRoute(); final bw = await DbHelper.getRatingByWeek(); if (mounted) setState(() { _stats = s; _byColonia = bc; _byRoute = br; _byWeek = bw; _loading = false; }); } @override Widget build(BuildContext context) => Scaffold( backgroundColor: AppColors.grisFondo, appBar: AppBar( backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white, title: const Text('Dashboard de Estadisticas'), bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado)), actions: [IconButton(icon: const Icon(Icons.refresh), onPressed: _load)], ), body: _loading ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView(padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // KPIs Row(children: [ _KpiCard('Reportes', '${_stats['total_reportes']}', Icons.report, AppColors.naranjaAlerta), const SizedBox(width: 8), _KpiCard('Calificacion Prom.', (_stats['avg_rating'] as double? ?? 0).toStringAsFixed(1), Icons.star, Colors.amber), ]), const SizedBox(height: 8), Row(children: [ _KpiCard('Alertas Activas', '${_stats['alertas_activas']}', Icons.warning, AppColors.rojoError), const SizedBox(width: 8), _KpiCard('Conductores', '${_stats['total_conductores']}', Icons.person, AppColors.guindaPrimary), ]), const SizedBox(height: 20), // Calificacion por semana (línea) if (_byWeek.isNotEmpty) ...[ _SectionTitle('Calificacion promedio semanal'), const SizedBox(height: 8), Card(child: Padding(padding: const EdgeInsets.all(16), child: SizedBox(height: 180, child: LineChart(LineChartData( minY: 1, maxY: 5, titlesData: FlTitlesData( leftTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (v,_) => Text(v.toInt().toString(), style: const TextStyle(fontSize: 10)))), bottomTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, getTitlesWidget: (v, _) { final idx = v.toInt(); if (idx < 0 || idx >= _byWeek.length) return const SizedBox(); return Text('S${_byWeek.length - idx}', style: const TextStyle(fontSize: 9)); })), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), gridData: FlGridData(drawHorizontalLine: true, horizontalInterval: 1), borderData: FlBorderData(show: true, border: Border.all(color: Colors.grey.shade300)), lineBarsData: [LineChartBarData( spots: _byWeek.reversed.toList().asMap().entries.map((e) => FlSpot(e.key.toDouble(), (e.value['promedio'] as num? ?? 0).toDouble().clamp(1.0, 5.0))).toList(), isCurved: true, color: AppColors.guindaPrimary, barWidth: 3, belowBarData: BarAreaData(show: true, color: AppColors.guindaPrimary.withOpacity(0.1)), dotData: const FlDotData(show: true), )], ))))), const SizedBox(height: 20), ], // Reportes por colonia (barras horizontales) if (_byColonia.isNotEmpty) ...[ _SectionTitle('Reportes por colonia (Top 10)'), const SizedBox(height: 8), Card(child: Padding(padding: const EdgeInsets.all(16), child: SizedBox(height: 240, child: BarChart(BarChartData( alignment: BarChartAlignment.spaceAround, maxY: (_byColonia.map((c) => (c['total'] as int? ?? 0).toDouble()) .reduce((a,b)=>a>b?a:b) * 1.2), titlesData: FlTitlesData( bottomTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: (v, _) { final i = v.toInt(); if (i < 0 || i >= _byColonia.length) return const SizedBox(); final name = (_byColonia[i]['colonia'] as String? ?? ''); return Transform.rotate(angle: -0.5, child: Text(name.length > 8 ? '${name.substring(0,8)}.' : name, style: const TextStyle(fontSize: 8))); })), leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, getTitlesWidget: (v,_) => Text(v.toInt().toString(), style: const TextStyle(fontSize: 9)))), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), barGroups: _byColonia.asMap().entries.map((e) => BarChartGroupData( x: e.key, barRods: [ BarChartRodData( toY: (e.value['total'] as int? ?? 0).toDouble(), color: AppColors.guindaPrimary, width: 16, borderRadius: BorderRadius.circular(4)), BarChartRodData( toY: (e.value['resueltos'] as int? ?? 0).toDouble(), color: AppColors.verdeExito, width: 16, borderRadius: BorderRadius.circular(4)), ], )).toList(), gridData: const FlGridData(drawHorizontalLine: true), borderData: FlBorderData(show: true, border: Border.all(color: Colors.grey.shade300)), ))))), Padding(padding: const EdgeInsets.symmetric(vertical: 6), child: Row(children: [ _Legend(AppColors.guindaPrimary, 'Total reportes'), const SizedBox(width: 16), _Legend(AppColors.verdeExito, 'Resueltos'), ])), const SizedBox(height: 20), ], // Rutas con más incidentes if (_byRoute.isNotEmpty) ...[ _SectionTitle('Rutas con mas incidentes'), const SizedBox(height: 8), Card(child: Padding(padding: const EdgeInsets.all(16), child: SizedBox(height: 200, child: BarChart(BarChartData( alignment: BarChartAlignment.spaceAround, maxY: (_byRoute.map((r) => (r['total'] as int? ?? 0).toDouble()) .reduce((a,b)=>a>b?a:b) * 1.3), titlesData: FlTitlesData( bottomTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, reservedSize: 28, getTitlesWidget: (v, _) { final i = v.toInt(); if (i < 0 || i >= _byRoute.length) return const SizedBox(); return Text((_byRoute[i]['route_id'] as String? ?? '').replaceAll('RUTA-','R'), style: const TextStyle(fontSize: 9)); })), leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, getTitlesWidget: (v,_) => Text(v.toInt().toString(), style: const TextStyle(fontSize: 9)))), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), barGroups: _byRoute.asMap().entries.map((e) => BarChartGroupData( x: e.key, barRods: [BarChartRodData( toY: (e.value['total'] as int? ?? 0).toDouble(), gradient: const LinearGradient( colors: [AppColors.naranjaAlerta, AppColors.rojoError], begin: Alignment.bottomCenter, end: Alignment.topCenter), width: 20, borderRadius: BorderRadius.circular(4))], )).toList(), gridData: const FlGridData(drawHorizontalLine: true), borderData: FlBorderData(show: true, border: Border.all(color: Colors.grey.shade300)), ))))), const SizedBox(height: 20), ], // Colonias más problemáticas (lista) _SectionTitle('Colonias mas problematicas'), const SizedBox(height: 8), Card(child: Column(children: [ ..._byColonia.take(5).map((c) { final total = (c['total'] as int? ?? 0); final resueltos = (c['resueltos'] as int? ?? 0); final pct = total > 0 ? resueltos / total : 0.0; return ListTile(dense: true, leading: CircleAvatar(radius: 16, backgroundColor: total > 3 ? AppColors.rojoError.withOpacity(0.15) : AppColors.naranjaAlerta.withOpacity(0.15), child: Text('$total', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: total > 3 ? AppColors.rojoError : AppColors.naranjaAlerta))), title: Text(c['colonia'] as String? ?? '', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)), subtitle: LinearProgressIndicator(value: pct, backgroundColor: Colors.grey.shade200, valueColor: const AlwaysStoppedAnimation(AppColors.verdeExito)), trailing: Text('${(pct*100).toInt()}% resuelto', style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)), ); }), ])), const SizedBox(height: 30), ])), ); } class _KpiCard extends StatelessWidget { final String label, value; final IconData icon; final Color color; const _KpiCard(this.label, this.value, this.icon, this.color); @override Widget build(BuildContext context) => Expanded(child: Card(child: Padding( padding: const EdgeInsets.all(14), child: Row(children: [ CircleAvatar(radius: 22, backgroundColor: color.withOpacity(0.12), child: Icon(icon, color: color, size: 22)), const SizedBox(width: 10), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color)), Text(label, style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)), ]), ])))); } class _SectionTitle extends StatelessWidget { final String title; const _SectionTitle(this.title); @override Widget build(BuildContext context) => Text(title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: AppColors.guindaPrimary)); } class _Legend extends StatelessWidget { final Color color; final String label; const _Legend(this.color, this.label); @override Widget build(BuildContext context) => Row(mainAxisSize: MainAxisSize.min, children: [ Container(width: 12, height: 12, decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(2))), const SizedBox(width: 4), Text(label, style: const TextStyle(fontSize: 11, color: AppColors.grisTexto)), ]); }