import 'package:flutter/material.dart'; import '../models/client.dart'; import '../models/activity.dart'; import '../models/app_stats.dart'; import '../theme/app_theme.dart'; import 'surveys_list_screen.dart'; class DashboardScreen extends StatefulWidget { final AppStats stats; final List activities; final List clients; final List surveys; final List pricing; final void Function(int) onNavigate; final VoidCallback onDataChanged; final bool backendReady; final VoidCallback onLogout; final bool isActive; const DashboardScreen({ super.key, required this.stats, required this.activities, required this.clients, this.surveys = const [], this.pricing = const [], required this.onNavigate, required this.onDataChanged, required this.backendReady, required this.onLogout, this.isActive = false, }); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State { final ScrollController _scrollController = ScrollController(); @override void didUpdateWidget(covariant DashboardScreen oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isActive && !oldWidget.isActive) { if (_scrollController.hasClients) { _scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); } } } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.fromLTRB(16, 20, 16, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ WideTopBanner( backendReady: widget.backendReady, subtitle: 'DASHBOARD', onLogout: widget.onLogout, ), const SizedBox(height: 20), _WelcomeHeader(), const SizedBox(height: 20), _BentoGrid(stats: widget.stats), const SizedBox(height: 20), _QuickActions(onNavigate: widget.onNavigate), const SizedBox(height: 20), _MarketInsights(surveys: widget.surveys, pricing: widget.pricing, onNavigate: widget.onNavigate, onDataChanged: widget.onDataChanged), const SizedBox(height: 20), _RecentActivity( activities: widget.activities, clients: widget.clients, onNavigate: widget.onNavigate, ), const SizedBox(height: 20), _InsightCard(), ], ), ); } } class _WelcomeHeader extends StatelessWidget { static String get _greeting { final h = DateTime.now().hour; if (h < 12) return 'Good Morning'; if (h < 17) return 'Good Afternoon'; if (h < 21) return 'Good Evening'; return 'Good Night'; } static String get _subtext { final h = DateTime.now().hour; if (h < 12) return 'Start your day with a fresh logistics survey.'; if (h < 17) return 'Keep the momentum log your field visits.'; if (h < 21) return 'Wrap up the day any pending surveys to log?'; return 'Late session active your data is synced.'; } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _greeting, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w800, color: AppColors.darkBlue, ), ), const SizedBox(height: 4), Text( _subtext, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ], ); } } class _BentoGrid extends StatelessWidget { final AppStats stats; const _BentoGrid({required this.stats}); @override Widget build(BuildContext context) { return Column( children: [ // Large card spanning full width Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color(0xFFD3E4FE), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFA6C8FF).withValues(alpha: 0.3)), ), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'TOTAL CLIENTS', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.primary, ), ), const SizedBox(height: 8), Text( stats.totalClients.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (m) => '${m[1]},', ), style: const TextStyle( fontSize: 36, fontWeight: FontWeight.w900, color: AppColors.darkBlue, ), ), const SizedBox(height: 12), Row( children: [ Icon(Icons.arrow_upward, size: 14, color: AppColors.greenDark), const SizedBox(width: 4), Text( '+12% from last month', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.greenDark, ), ), ], ), ], ), Positioned( right: -20, bottom: -20, child: Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.primary.withValues(alpha: 0.06), ), ), ), ], ), ), const SizedBox(height: 12), Row( children: [ Expanded( child: _SmallStatCard( icon: Icons.calendar_today, iconColor: AppColors.primary, value: stats.todayEntries.toString(), label: "Today's Entries", ), ), const SizedBox(width: 12), Expanded( child: _SmallStatCard( icon: Icons.access_time, iconColor: const Color(0xFF004D47), value: stats.pendingFollowups.toString(), label: 'Pending Follow-ups', ), ), ], ), ], ); } } class _SmallStatCard extends StatelessWidget { final IconData icon; final Color iconColor; final String value; final String label; const _SmallStatCard({ required this.icon, required this.iconColor, required this.value, required this.label, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border.withValues(alpha: 0.35)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 24, color: iconColor), const SizedBox(height: 10), Text( value, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.w700, color: AppColors.darkBlue, ), ), const SizedBox(height: 2), Text( label, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ); } } class _QuickActions extends StatelessWidget { final void Function(int) onNavigate; const _QuickActions({required this.onNavigate}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'QUICK ACTIONS', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.2, color: AppColors.textSecondary, ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, height: 48, child: ElevatedButton.icon( onPressed: () => onNavigate(4), icon: const Icon(Icons.add, size: 18, color: Colors.white), label: const Text( 'Quick Add Client', style: TextStyle( fontWeight: FontWeight.w600, color: Colors.white, fontSize: 14, ), ), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), elevation: 1, ), ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, height: 48, child: ElevatedButton.icon( onPressed: () => onNavigate(1), icon: Icon(Icons.search, size: 18, color: AppColors.darkBlue), label: Text( 'Search Client Database', style: TextStyle( fontWeight: FontWeight.w600, color: AppColors.darkBlue, fontSize: 14, ), ), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFD3E4FE), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: const BorderSide(color: Color(0xFFA6C8FF)), ), elevation: 0, ), ), ), ], ); } } class _MarketInsights extends StatelessWidget { final List surveys; final List pricing; final void Function(int) onNavigate; final VoidCallback onDataChanged; const _MarketInsights({required this.surveys, required this.pricing, required this.onNavigate, required this.onDataChanged}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'MARKET DATA', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.2, color: AppColors.textSecondary, ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute( builder: (ctx) => SurveysListScreen( surveys: surveys, onDataChanged: onDataChanged, ), )); }, child: _SmallStatCard( icon: Icons.storefront_outlined, iconColor: const Color(0xFFE87C00), value: surveys.length.toString(), label: 'Competitor Surveys', ), ), ), const SizedBox(width: 12), Expanded( child: GestureDetector( onTap: () => onNavigate(3), child: _SmallStatCard( icon: Icons.price_change_outlined, iconColor: const Color(0xFF15803D), value: pricing.length.toString(), label: 'Carrier Pricing', ), ), ), ], ), ], ); } } class _RecentActivity extends StatelessWidget { final List activities; final List clients; final void Function(int) onNavigate; const _RecentActivity({ required this.activities, required this.clients, required this.onNavigate, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'RECENT ACTIVITY', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.2, color: AppColors.textSecondary, ), ), GestureDetector( onTap: () => onNavigate(1), child: Text( 'View all', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.primary, ), ), ), ], ), const SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border.withValues(alpha: 0.2)), ), child: Column( children: activities .take(5) .map((activity) => _ActivityTile( activity: activity, onTap: () => onNavigate(1), isLast: activity == activities.last, )) .toList(), ), ), ], ); } } class _ActivityTile extends StatelessWidget { final Activity activity; final VoidCallback onTap; final bool isLast; const _ActivityTile({ required this.activity, required this.onTap, required this.isLast, }); @override Widget build(BuildContext context) { Color iconBg; Color iconColor; IconData iconData; switch (activity.icon) { case ActivityIcon.shipping: iconBg = const Color(0xFFD5E0F8); iconColor = const Color(0xFF1E3A8A); iconData = Icons.local_shipping; break; case ActivityIcon.call: iconBg = const Color(0xFFFFE4E6); iconColor = AppColors.primary; iconData = Icons.phone; break; case ActivityIcon.edit: iconBg = const Color(0xFFDCFCE7); iconColor = const Color(0xFF15803D); iconData = Icons.edit; break; } Widget? badge; if (activity.status == ActivityStatus.newStatus) { badge = _Badge(label: 'NEW', bg: const Color(0xFFFFDAD7), fg: const Color(0xFF930013)); } else if (activity.status == ActivityStatus.pending) { badge = _Badge(label: 'PENDING', bg: const Color(0xFFD8E3FB), fg: const Color(0xFF3C475A)); } else if (activity.status == ActivityStatus.completed) { badge = _Badge(label: 'DONE', bg: const Color(0xFFD8F9EE), fg: const Color(0xFF115E59)); } return InkWell( onTap: onTap, borderRadius: isLast ? const BorderRadius.only( bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16), ) : null, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( border: isLast ? null : Border( bottom: BorderSide( color: AppColors.border.withValues(alpha: 0.15), ), ), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: iconBg, shape: BoxShape.circle, ), child: Icon(iconData, size: 18, color: iconColor), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( activity.clientName, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.darkBlue, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( activity.action, style: TextStyle( fontSize: 11, color: AppColors.textSecondary, ), ), ], ), ), ?badge, ], ), ), ); } } class _Badge extends StatelessWidget { final String label; final Color bg; final Color fg; const _Badge({required this.label, required this.bg, required this.fg}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(20), ), child: Text( label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: fg, ), ), ); } } class _InsightCard extends StatelessWidget { @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( width: double.infinity, height: 160, child: Stack( fit: StackFit.expand, children: [ Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF0B1C30), Color(0xFF8D0012)], ), ), ), Positioned.fill( child: CustomPaint(painter: _GridPainter()), ), Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ Row( children: [ Icon(Icons.auto_awesome, size: 12, color: const Color(0xFFFFDAD7)), const SizedBox(width: 4), Text( 'INSIGHT OF THE WEEK', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 1.2, color: const Color(0xFFFFDAD7), ), ), ], ), const SizedBox(height: 6), const Text( 'Weekly Performance Report', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: Colors.white, ), ), const SizedBox(height: 4), Text( "Analyze your team's survey conversion rates.", style: TextStyle( fontSize: 11, color: Colors.white.withValues(alpha: 0.8), fontWeight: FontWeight.w300, ), ), ], ), ), ], ), ), ); } } class _GridPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white.withValues(alpha: 0.04) ..strokeWidth = 1; const spacing = 30.0; for (double x = 0; x < size.width; x += spacing) { canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); } for (double y = 0; y < size.height; y += spacing) { canvas.drawLine(Offset(0, y), Offset(size.width, y), paint); } } @override bool shouldRepaint(_GridPainter oldDelegate) => false; }