import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mileograph_flutter/components/dashboard/leaderboardPanel.dart'; import 'package:mileograph_flutter/components/dashboard/topTractionPanel.dart'; import 'package:mileograph_flutter/objects/objects.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/dataService.dart'; import 'package:provider/provider.dart'; class Dashboard extends StatelessWidget { const Dashboard({super.key}); @override Widget build(BuildContext context) { final data = context.watch(); final auth = context.watch(); final stats = data.homepageStats; return RefreshIndicator( onRefresh: () async { await data.fetchHomepageStats(); await Future.wait([ data.fetchOnThisDay(), data.fetchTripDetails(), data.fetchHadTraction(), ]); }, child: LayoutBuilder( builder: (context, constraints) { final isWide = constraints.maxWidth > 1100; final metricChips = _buildMetricChips( context, totalMileage: stats?.totalMileage ?? 0, currentYearMileage: data.getMileageForCurrentYear(), trips: data.trips.length, ); return ListView( padding: const EdgeInsets.all(16), children: [ _buildHeader(context, auth, stats, data.isHomepageLoading), const SizedBox(height: 12), Wrap( spacing: 12, runSpacing: 12, children: metricChips, ), const SizedBox(height: 16), isWide ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildMainColumn(context, data)), const SizedBox(width: 16), SizedBox( width: 360, child: _buildSidebar(context, data), ), ], ) : Column( children: [ _buildMainColumn(context, data), const SizedBox(height: 16), _buildSidebar(context, data), ], ), ], ); }, ), ); } Widget _buildHeader(BuildContext context, AuthService auth, HomepageStats? stats, bool loading) { final greetingName = stats?.user?.full_name ?? auth.fullName ?? auth.username ?? 'there'; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Dashboard', style: Theme.of(context).textTheme.labelMedium, ), const SizedBox(height: 2), Text( 'Welcome back, $greetingName', style: Theme.of(context).textTheme.headlineSmall, ), ], ), if (loading) const Padding( padding: EdgeInsets.only(right: 8.0), child: SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ), ], ); } List _buildMetricChips( BuildContext context, { required double totalMileage, required double currentYearMileage, required int trips, }) { final textTheme = Theme.of(context).textTheme; Widget metricCard(String label, String value) { return Card( elevation: 1, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(label.toUpperCase(), style: textTheme.labelSmall?.copyWith( letterSpacing: 0.7, color: textTheme.bodySmall?.color?.withOpacity(0.7), )), const SizedBox(height: 4), Text( value, style: textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), ], ), ), ); } return [ metricCard('Total mileage', '${totalMileage.toStringAsFixed(1)} mi'), metricCard('This year', '${currentYearMileage.toStringAsFixed(1)} mi'), metricCard('Trips logged', trips.toString()), ]; } Widget _buildMainColumn(BuildContext context, DataService data) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildCard( context, title: 'On this day', trailing: data.isOnThisDayLoading ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : null, child: _buildLegList( context, data.onThisDay, emptyMessage: 'No historical moves for today yet.', ), ), const SizedBox(height: 12), _buildQuickCalcCard(context), const SizedBox(height: 12), _buildTripsCard(context, data), ], ); } Widget _buildSidebar(BuildContext context, DataService data) { return Column( children: [ TopTractionPanel(), const SizedBox(height: 12), LeaderboardPanel(), ], ); } Widget _buildCard( BuildContext context, { required String title, required Widget child, Widget? trailing, Widget? action, }) { return Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700), ), Row( mainAxisSize: MainAxisSize.min, children: [ if (action != null) action, if (trailing != null) ...[ const SizedBox(width: 8), trailing, ], ], ), ], ), const SizedBox(height: 12), child, ], ), ), ); } Widget _buildLegList( BuildContext context, List legs, { required String emptyMessage, }) { if (legs.isEmpty) { return Text( emptyMessage, style: Theme.of(context).textTheme.bodyMedium, ); } return Column( children: legs.take(5).map((leg) { return ListTile( dense: true, contentPadding: EdgeInsets.zero, leading: CircleAvatar( backgroundColor: Theme.of(context).colorScheme.primaryContainer, child: const Icon(Icons.train), ), title: Text('${leg.start} → ${leg.end}'), subtitle: Text(_formatDate(leg.beginTime)), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${leg.mileage.toStringAsFixed(1)} mi'), if (leg.headcode.isNotEmpty) Text( leg.headcode, style: Theme.of(context) .textTheme .labelSmall ?.copyWith(color: Theme.of(context).hintColor), ), ], ), ); }).toList(), ); } Widget _buildQuickCalcCard(BuildContext context) { return _buildCard( context, title: 'Quick mileage calculator', action: TextButton.icon( onPressed: () => context.push('/calculator'), icon: const Icon(Icons.open_in_new), label: const Text('Open calculator'), ), child: Text( 'Jump into the route calculator to quickly total a journey before saving it.', style: Theme.of(context).textTheme.bodyMedium, ), ); } Widget _buildTripsCard(BuildContext context, DataService data) { final trips = data.trips; return _buildCard( context, title: 'Trips', action: TextButton( onPressed: () => context.push('/trips'), child: const Text('View all'), ), child: trips.isEmpty ? Text( 'No trips logged yet. Add one from the Trips page.', style: Theme.of(context).textTheme.bodyMedium, ) : Column( children: trips.take(5).map((trip) { return ListTile( contentPadding: EdgeInsets.zero, title: Text(trip.tripName), subtitle: Text('${trip.tripMileage.toStringAsFixed(1)} mi'), trailing: const Icon(Icons.chevron_right), ); }).toList(), ), ); } String _formatDate(DateTime? dt) { if (dt == null) return ''; return '${dt.year.toString().padLeft(4, '0')}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}'; } }