import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:share_plus/share_plus.dart'; import '../../core/app_colors.dart'; import '../../database/db_helper.dart'; class ExportPdfScreen extends StatefulWidget { const ExportPdfScreen({super.key}); @override State createState() => _ExportPdfScreenState(); } class _ExportPdfScreenState extends State { bool _generating = false; String? _lastPath; pw.TableRow _pdfRow(String label, String value, {bool isHeader = false}) => pw.TableRow( decoration: isHeader ? pw.BoxDecoration(color: PdfColors.grey100) : null, children: [ pw.Padding(padding: const pw.EdgeInsets.all(6), child: pw.Text(label, style: pw.TextStyle( fontSize: 10, fontWeight: isHeader ? pw.FontWeight.bold : null))), pw.Padding(padding: const pw.EdgeInsets.all(6), child: pw.Text(value, style: pw.TextStyle( fontSize: 10, fontWeight: pw.FontWeight.bold))), ]); Future _generatePdf() async { setState(() => _generating = true); try { final stats = await DbHelper.getAdminStats(); final colonias = await DbHelper.getReportesByColonia(); final incidentes = await DbHelper.getIncidentesByRoute(); final reviews = await DbHelper.getReviewSummaryByColonia(); final now = DateTime.now(); const meses = ['','Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; final pdf = pw.Document(); pdf.addPage(pw.MultiPage( pageFormat: PdfPageFormat.a4, margin: const pw.EdgeInsets.all(32), header: (ctx) => pw.Column(children: [ pw.Row(mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Column(crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text('H. AYUNTAMIENTO DE CELAYA', style: pw.TextStyle( fontSize: 14, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.Text('Direccion de Servicios Publicos', style: const pw.TextStyle(fontSize: 11, color: PdfColors.grey700)), pw.Text('Sistema de Recoleccion de Residuos', style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey600)), ]), pw.Column(crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text('REPORTE MENSUAL', style: pw.TextStyle( fontSize: 12, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.Text('${meses[now.month]} ${now.year}', style: const pw.TextStyle(fontSize: 11)), pw.Text('Generado: ${now.day}/${now.month}/${now.year}', style: const pw.TextStyle(fontSize: 9, color: PdfColors.grey600)), ]), ]), pw.Divider(color: PdfColor.fromHex('C9A84C'), thickness: 2), pw.SizedBox(height: 8), ]), build: (ctx) => [ pw.Text('RESUMEN EJECUTIVO', style: pw.TextStyle( fontSize: 13, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.SizedBox(height: 8), pw.Table( border: pw.TableBorder.all(color: PdfColors.grey300), columnWidths: {0: const pw.FlexColumnWidth(2), 1: const pw.FlexColumnWidth(1)}, children: [ _pdfRow('Total de reportes ciudadanos', '${stats["total_reportes"]}', isHeader: true), _pdfRow('Total de resenas recibidas', '${stats["total_reviews"]}'), _pdfRow('Calificacion promedio', '${(stats["avg_rating"] as double? ?? 0).toStringAsFixed(2)} / 5.0'), _pdfRow('Alertas activas', '${stats["alertas_activas"]}'), _pdfRow('Conductores', '${stats["total_conductores"]}'), ]), pw.SizedBox(height: 20), if (colonias.isNotEmpty) ...[ pw.Text('REPORTES POR COLONIA', style: pw.TextStyle( fontSize: 13, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.SizedBox(height: 8), pw.Table( border: pw.TableBorder.all(color: PdfColors.grey300), columnWidths: { 0: const pw.FlexColumnWidth(3), 1: const pw.FlexColumnWidth(1), 2: const pw.FlexColumnWidth(1), 3: const pw.FlexColumnWidth(1), }, children: [ pw.TableRow( decoration: pw.BoxDecoration(color: PdfColor.fromHex('6D1E3A')), children: ['Colonia','Total','Resueltos','Pendientes'].map((h) => pw.Padding(padding: const pw.EdgeInsets.all(6), child: pw.Text(h, style: pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 10)))).toList()), ...colonias.map((c) { final total = c['total'] as int? ?? 0; final res = c['resueltos'] as int? ?? 0; return pw.TableRow(children: [ c['colonia'] as String? ?? '', '$total', '$res', '${total - res}', ].map((v) => pw.Padding(padding: const pw.EdgeInsets.all(5), child: pw.Text(v, style: const pw.TextStyle(fontSize: 9)))).toList()); }), ]), pw.SizedBox(height: 20), ], if (incidentes.isNotEmpty) ...[ pw.Text('INCIDENTES POR RUTA', style: pw.TextStyle( fontSize: 13, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.SizedBox(height: 8), pw.Table( border: pw.TableBorder.all(color: PdfColors.grey300), columnWidths: {0: const pw.FlexColumnWidth(2), 1: const pw.FlexColumnWidth(1)}, children: [ pw.TableRow(decoration: pw.BoxDecoration(color: PdfColor.fromHex('6D1E3A')), children: ['Ruta','Incidentes'].map((h) => pw.Padding( padding: const pw.EdgeInsets.all(6), child: pw.Text(h, style: pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 10)))).toList()), ...incidentes.map((r) => pw.TableRow(children: [ r['route_id'] as String? ?? '', '${r["total"]}', ].map((v) => pw.Padding(padding: const pw.EdgeInsets.all(5), child: pw.Text(v, style: const pw.TextStyle(fontSize: 9)))).toList())), ]), pw.SizedBox(height: 20), ], if (reviews.isNotEmpty) ...[ pw.Text('CALIFICACIONES POR COLONIA', style: pw.TextStyle( fontSize: 13, fontWeight: pw.FontWeight.bold, color: PdfColor.fromHex('6D1E3A'))), pw.SizedBox(height: 8), pw.Table( border: pw.TableBorder.all(color: PdfColors.grey300), columnWidths: {0: const pw.FlexColumnWidth(3), 1: const pw.FlexColumnWidth(1), 2: const pw.FlexColumnWidth(1)}, children: [ pw.TableRow(decoration: pw.BoxDecoration(color: PdfColor.fromHex('6D1E3A')), children: ['Colonia','Promedio','Total'].map((h) => pw.Padding( padding: const pw.EdgeInsets.all(6), child: pw.Text(h, style: pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 10)))).toList()), ...reviews.map((r) => pw.TableRow(children: [ r['colonia'] as String? ?? '', '${(r["promedio"] as num? ?? 0).toStringAsFixed(1)}/5', '${r["total"]}', ].map((v) => pw.Padding(padding: const pw.EdgeInsets.all(5), child: pw.Text(v, style: const pw.TextStyle(fontSize: 9)))).toList())), ]), pw.SizedBox(height: 20), ], pw.Divider(color: PdfColor.fromHex('C9A84C')), pw.Text('Celaya Limpia - H. Ayuntamiento de Celaya, Gto. - ${now.year}', style: const pw.TextStyle(fontSize: 8, color: PdfColors.grey500), textAlign: pw.TextAlign.center), ], )); // Guardar en directorio temporal y compartir con share_plus final bytes = await pdf.save(); final dir = await getTemporaryDirectory(); final file = File('${dir.path}/reporte_celaya_${now.month}_${now.year}.pdf'); await file.writeAsBytes(bytes); setState(() => _lastPath = file.path); await Share.shareXFiles( [XFile(file.path, mimeType: 'application/pdf')], subject: 'Reporte Mensual Celaya Limpia - ${meses[now.month]} ${now.year}', ); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error al generar PDF: $e'), backgroundColor: AppColors.rojoError)); } if (mounted) setState(() => _generating = false); } @override Widget build(BuildContext context) => Scaffold( backgroundColor: AppColors.grisFondo, appBar: AppBar( backgroundColor: AppColors.verdeAdmin, foregroundColor: Colors.white, title: const Text('Exportar Reporte PDF'), bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado))), body: Center(child: Padding(padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container(width: 100, height: 100, decoration: BoxDecoration(color: AppColors.verdeAdmin.withOpacity(0.1), shape: BoxShape.circle), child: const Icon(Icons.picture_as_pdf, size: 52, color: AppColors.verdeAdmin)), const SizedBox(height: 24), const Text('Reporte Mensual', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: AppColors.verdeAdmin)), const SizedBox(height: 8), const Text('Genera un PDF con el resumen completo:\nreportes, incidentes y calificaciones.', textAlign: TextAlign.center, style: TextStyle(color: AppColors.grisTexto)), const SizedBox(height: 32), SizedBox(width: double.infinity, height: 52, child: ElevatedButton.icon( onPressed: _generating ? null : _generatePdf, style: ElevatedButton.styleFrom( backgroundColor: AppColors.verdeAdmin, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), icon: _generating ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Icon(Icons.download), label: Text(_generating ? 'Generando...' : 'Generar y Compartir PDF', style: const TextStyle(fontWeight: FontWeight.bold)))), if (_lastPath != null) ...[ const SizedBox(height: 16), Container(padding: const EdgeInsets.all(10), decoration: BoxDecoration(color: AppColors.verdeExito.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColors.verdeExito.withOpacity(0.3))), child: Row(children: [ const Icon(Icons.check_circle, color: AppColors.verdeExito, size: 18), const SizedBox(width: 8), const Expanded(child: Text('PDF generado correctamente', style: TextStyle(color: AppColors.verdeExito, fontWeight: FontWeight.w600, fontSize: 13))), TextButton(onPressed: _generatePdf, child: const Text('Compartir de nuevo', style: TextStyle(fontSize: 11, color: AppColors.verdeAdmin))), ])), ], ])))); }