import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:nethive_neo/providers/videos_provider.dart'; import 'package:nethive_neo/theme/theme.dart'; import 'package:gap/gap.dart'; class DashboardPage extends StatefulWidget { const DashboardPage({Key? key}) : super(key: key); @override State createState() => _DashboardPageState(); } class _DashboardPageState extends State with TickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; Map stats = {}; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); _animationController.forward(); _loadStats(); } Future _loadStats() async { final provider = Provider.of(context, listen: false); final result = await provider.getDashboardStats(); setState(() { stats = result; }); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width <= 800; return FadeTransition( opacity: _fadeAnimation, child: SingleChildScrollView( padding: EdgeInsets.all(isMobile ? 16 : 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const Gap(24), _buildStatsCards(isMobile), const Gap(24), if (!isMobile) ...[ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildCategoryChart()), const Gap(24), Expanded(child: _buildRecentActivity()), ], ), ] else ...[ _buildCategoryChart(), const Gap(24), _buildRecentActivity(), ], ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: AppTheme.of(context).primaryGradient, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppTheme.of(context).primaryColor.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 5), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.dashboard, size: 32, color: Colors.white, ), ), const Gap(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Dashboard MDF/IDF', style: AppTheme.of(context).title1.override( fontFamily: 'Poppins', color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), const Gap(4), Text( 'Panel de control de contenido multimedia', style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: Colors.white.withOpacity(0.9), fontSize: 14, ), ), ], ), ), ], ), ); } Widget _buildStatsCards(bool isMobile) { return isMobile ? Column( children: [ _buildStatCard( 'Videos Totales', stats['total_videos']?.toString() ?? '0', Icons.video_library, AppTheme.of(context).primaryColor, ), const Gap(16), _buildStatCard( 'Reproducciones', stats['total_reproducciones']?.toString() ?? '0', Icons.play_circle_filled, AppTheme.of(context).secondaryColor, ), const Gap(16), _buildStatCard( 'Categorías', stats['total_categories']?.toString() ?? '0', Icons.category, AppTheme.of(context).tertiaryColor, ), const Gap(16), _buildStatCard( 'Video más visto', stats['most_viewed_video']?['title'] ?? 'N/A', Icons.trending_up, AppTheme.of(context).error, ), ], ) : Row( children: [ Expanded( child: _buildStatCard( 'Videos Totales', stats['total_videos']?.toString() ?? '0', Icons.video_library, AppTheme.of(context).primaryColor, ), ), const Gap(16), Expanded( child: _buildStatCard( 'Reproducciones', stats['total_reproducciones']?.toString() ?? '0', Icons.play_circle_filled, AppTheme.of(context).secondaryColor, ), ), const Gap(16), Expanded( child: _buildStatCard( 'Categorías', stats['total_categories']?.toString() ?? '0', Icons.category, AppTheme.of(context).tertiaryColor, ), ), const Gap(16), Expanded( child: _buildStatCard( 'Video más visto', stats['most_viewed_video']?['title'] ?? 'N/A', Icons.trending_up, AppTheme.of(context).error, ), ), ], ); } Widget _buildStatCard( String title, String value, IconData icon, Color color) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(12), border: Border.all( color: color.withOpacity(0.3), ), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( icon, color: color, size: 24, ), ), ], ), const Gap(16), Text( value, style: AppTheme.of(context).title1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontSize: 28, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const Gap(4), Text( title, style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).secondaryText, fontSize: 14, ), ), ], ), ); } Widget _buildCategoryChart() { final categoriesMap = stats['videos_by_category'] as Map?; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppTheme.of(context).primaryColor.withOpacity(0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.pie_chart, color: AppTheme.of(context).primaryColor, ), const Gap(8), Text( 'Distribución por Categoría', style: AppTheme.of(context).title3.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontWeight: FontWeight.bold, ), ), ], ), const Gap(20), if (categoriesMap != null && categoriesMap.isNotEmpty) ...categoriesMap.entries.map( (entry) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildCategoryBar( entry.key, entry.value, categoriesMap.values.reduce((a, b) => a > b ? a : b), ), ), ) else Center( child: Padding( padding: const EdgeInsets.all(40), child: Text( 'No hay datos de categorías', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, ), ), ), ), ], ), ); } Widget _buildCategoryBar(String category, int count, int maxCount) { final percentage = maxCount > 0 ? count / maxCount : 0.0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( category, style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, ), ), Text( count.toString(), style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), ], ), const Gap(8), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: percentage, backgroundColor: AppTheme.of(context).tertiaryBackground, valueColor: AlwaysStoppedAnimation( AppTheme.of(context).primaryColor, ), minHeight: 8, ), ), ], ); } Widget _buildRecentActivity() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppTheme.of(context).primaryColor.withOpacity(0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.access_time, color: AppTheme.of(context).primaryColor, ), const Gap(8), Text( 'Actividad Reciente', style: AppTheme.of(context).title3.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontWeight: FontWeight.bold, ), ), ], ), const Gap(20), Consumer( builder: (context, provider, child) { if (provider.mediaFiles.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Text( 'No hay actividad reciente', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, ), ), ), ); } final recentVideos = provider.mediaFiles.take(5).toList(); return Column( children: recentVideos.map((video) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: AppTheme.of(context) .primaryColor .withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.video_library, color: AppTheme.of(context).primaryColor, ), ), const Gap(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( video.title ?? video.fileName, style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const Gap(4), Text( 'Hace ${_getTimeAgo(video.createdAt)}', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, fontSize: 12, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: AppTheme.of(context) .secondaryColor .withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${video.reproducciones} views', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).secondaryColor, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), ); }).toList(), ); }, ), ], ), ); } String _getTimeAgo(DateTime? date) { if (date == null) return 'desconocido'; final now = DateTime.now(); final difference = now.difference(date); if (difference.inDays > 7) { return '${(difference.inDays / 7).floor()} semanas'; } else if (difference.inDays > 0) { return '${difference.inDays} días'; } else if (difference.inHours > 0) { return '${difference.inHours} horas'; } else if (difference.inMinutes > 0) { return '${difference.inMinutes} minutos'; } else { return 'hace un momento'; } } }