import 'package:siro_admin/constant/theme.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart' hide TextDirection; import 'package:siro_admin/controller/admin/static_controller.dart'; import 'notes_driver_page.dart'; class StaticDash extends StatelessWidget { const StaticDash({super.key}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: cs.surface, body: GetBuilder( builder: (c) { return CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ _SliverHeader(controller: c, cs: cs, isDark: isDark), if (c.isLoading) SliverFillRemaining(child: _LoadingState(cs: cs)) else ...[ SliverToBoxAdapter( child: _DateBadge(controller: c, cs: cs), ), SliverToBoxAdapter( child: _KpiRow(controller: c, cs: cs), ), const SliverToBoxAdapter( child: _SectionLabel('نظرة عامة على النمو', Icons.trending_up_rounded), ), SliverToBoxAdapter( child: AnimationLimiter( child: Column( children: AnimationConfiguration.toStaggeredList( duration: const Duration(milliseconds: 450), childAnimationBuilder: (w) => SlideAnimation( verticalOffset: 40, child: FadeInAnimation(child: w), ), children: [ _ChartCard( title: 'الركاب', total: c.totalMonthlyPassengers, spots: c.chartDataPassengers, compareSpots: c.isComparing ? c.chartDataPassengersCompare : null, color: cs.tertiary, icon: Icons.groups_rounded, controller: c, cs: cs, ), const SizedBox(height: 12), _ChartCard( title: 'السائقون', total: c.totalMonthlyDrivers, spots: c.chartDataDrivers, compareSpots: c.isComparing ? c.chartDataDriversCompare : null, color: cs.warning, icon: Icons.drive_eta_rounded, controller: c, cs: cs, ), const SizedBox(height: 12), _ChartCard( title: 'الرحلات', total: c.totalMonthlyRides, spots: c.chartDataRides, compareSpots: c.isComparing ? c.chartDataRidesCompare : null, color: cs.primary, icon: Icons.map_rounded, controller: c, cs: cs, ), const SizedBox(height: 24), ], ), ), ), ), const SliverToBoxAdapter( child: _SectionLabel('أداء فريق العمل', Icons.workspaces_filled), ), SliverToBoxAdapter( child: Column( children: [ _DynamicMultiLineCard( title: 'تفعيل السائقين', subtitle: 'Activations', icon: Icons.how_to_reg_rounded, controller: c, getSpots: (emp) => emp.notesSpots, getCompareSpots: (emp) => c.employeeDataCompare[emp.name]?.notesSpots ?? [], getTotal: (emp) => emp.totalNotes, cs: cs, ), const SizedBox(height: 12), _DynamicMultiLineCard( title: 'عدد المكالمات', subtitle: 'Calls', icon: Icons.phone_in_talk_rounded, controller: c, getSpots: (emp) => emp.callsSpots, getCompareSpots: (emp) => c.employeeDataCompare[emp.name]?.callsSpots ?? [], getTotal: (emp) => emp.totalCalls, onTap: () => Get.to(() => const DailyNotesView()), cs: cs, ), const SizedBox(height: 24), ], ), ), if (c.employmentStatsList.isNotEmpty) ...[ const SliverToBoxAdapter( child: _SectionLabel('لوحة الصدارة', Icons.emoji_events_rounded), ), SliverToBoxAdapter( child: _EmployeeLeaderboard(stats: c.employmentStatsList, cs: cs), ), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], const SliverToBoxAdapter( child: _SectionLabel('متابعة التسجيل', Icons.assignment_turned_in_rounded), ), SliverToBoxAdapter( child: Column( children: [ _ChartCard( title: 'سائقين بعد الاتصال', total: c.staticList.isNotEmpty && c.staticList[0]['totalMonthlyMatchingNotes'] != null ? c.staticList[0]['totalMonthlyMatchingNotes'] .toString() : '0', spots: c.chartDataDriversMatchingNotes, compareSpots: c.isComparing ? c.chartDataDriversMatchingNotesCompare : null, color: cs.success, icon: Icons.phone_in_talk_rounded, controller: c, cs: cs, ), const SizedBox(height: 60), ], ), ), ], ], ); }, ), ); } } // ─── SLIVER HEADER ─────────────────────────────────────────────────────────── class _SliverHeader extends StatelessWidget { final StaticController controller; final ColorScheme cs; final bool isDark; const _SliverHeader({required this.controller, required this.cs, required this.isDark}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return SliverAppBar( expandedHeight: 100, pinned: true, floating: true, backgroundColor: cs.surface, elevation: 0, leading: GestureDetector( onTap: () => Get.back(), child: Container( margin: const EdgeInsets.all(10), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.outline), ), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurfaceVariant, size: 16), ), ), flexibleSpace: FlexibleSpaceBar( background: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [cs.primary.withValues(alpha: 0.06), cs.surface] : [cs.primary.withValues(alpha: 0.04), cs.surface], ), ), child: Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.primary.withValues(alpha: 0.25)), ), child: Icon(Icons.bar_chart_rounded, color: cs.primary, size: 18), ), const SizedBox(width: 10), ShaderMask( shaderCallback: (b) => LinearGradient( colors: [cs.primary, cs.tertiary], ).createShader(b), child: const Text( 'لوحة الإحصائيات', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), ), ], ), ), ), ), ), actions: [ GestureDetector( onTap: () async => await controller.getAll(), child: Container( margin: const EdgeInsets.only(right: 16, top: 10, bottom: 10), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.outline), ), child: Icon(Icons.refresh_rounded, color: cs.onSurfaceVariant, size: 18), ), ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(1), child: Container(height: 1, color: cs.outline), ), ); } } // ─── DATE BADGE ────────────────────────────────────────────────────────────── class _DateBadge extends StatelessWidget { final StaticController controller; final ColorScheme cs; const _DateBadge({required this.controller, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), border: Border.all(color: cs.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.calendar_today_rounded, color: cs.primary, size: 13), const SizedBox(width: 7), Text( controller.currentDateString, style: TextStyle( color: cs.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), if (controller.isComparing) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), border: Border.all(color: cs.outline.withValues(alpha: 0.5)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 6, height: 6, decoration: BoxDecoration( shape: BoxShape.circle, color: cs.onSurfaceVariant, ), ), const SizedBox(width: 7), Text( controller.compareDateString, style: TextStyle( color: cs.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.w500), ), ], ), ), ] ], ), ); } } // ─── KPI ROW ───────────────────────────────────────────────────────────────── class _KpiRow extends StatelessWidget { final StaticController controller; final ColorScheme cs; const _KpiRow({required this.controller, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final items = [ _KpiItem('الركاب', controller.totalMonthlyPassengers, Icons.groups_rounded, cs.tertiary), _KpiItem('السائقون', controller.totalMonthlyDrivers, Icons.drive_eta_rounded, cs.warning), _KpiItem( 'الرحلات', controller.totalMonthlyRides, Icons.map_rounded, cs.primary), ]; return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Row( children: items .map((item) => Expanded( child: Padding( padding: EdgeInsets.only(right: item != items.last ? 8 : 0), child: _buildKpiCard(item), ), )) .toList(), ), ); } Widget _buildKpiCard(_KpiItem item) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), border: Border.all(color: item.color.withValues(alpha: 0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(item.icon, color: item.color, size: 18), const SizedBox(height: 8), Text( item.value, style: TextStyle( color: cs.onSurface, fontSize: 20, fontWeight: FontWeight.w800, height: 1, ), ), const SizedBox(height: 3), Text(item.label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)), ], ), ); } } class _KpiItem { final String label, value; final IconData icon; final Color color; const _KpiItem(this.label, this.value, this.icon, this.color); } // ─── SECTION LABEL ─────────────────────────────────────────────────────────── class _SectionLabel extends StatelessWidget { final String text; final IconData icon; const _SectionLabel(this.text, this.icon); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 12), child: Row( children: [ Container( width: 3, height: 16, decoration: BoxDecoration( color: cs.primary, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 10), Icon(icon, size: 16, color: cs.onSurfaceVariant), const SizedBox(width: 8), Text( text, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w700, letterSpacing: 0.3, ), ), ], ), ); } } // ─── CHART CARD (Theme-aware, premium) ─────────────────────────────────────── class _ChartCard extends StatelessWidget { final String title, total; final List spots; final List? compareSpots; final Color color; final IconData icon; final StaticController controller; final ColorScheme cs; const _ChartCard({ required this.title, required this.total, required this.spots, this.compareSpots, required this.color, required this.icon, required this.controller, required this.cs, }); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final allSpots = [...spots, ...?compareSpots]; final maxY = _maxY(allSpots); final interval = _interval(maxY); final days = _days(controller); final xInterval = _xInterval(days); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( height: 340, decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), border: Border.all(color: color.withValues(alpha: 0.15)), boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.06), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Column( children: [ Padding( padding: const EdgeInsets.all(18), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [ Container( padding: const EdgeInsets.all(9), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withValues(alpha: 0.25)), ), child: Icon(icon, color: color, size: 18), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w700)), Text('الإجمالي: $total', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 11)), ]), ]), if (controller.isComparing) Row(children: [ Container( width: 16, height: 2, decoration: BoxDecoration( color: cs.onSurfaceVariant, borderRadius: BorderRadius.circular(1), )), const SizedBox(width: 5), Text('سابق', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 10)), ]), ], ), ), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(4, 0, 12, 10), child: Directionality( textDirection: TextDirection.ltr, child: LineChart(LineChartData( minX: 1, maxX: days > 0 ? days : 1, minY: 0, maxY: maxY, lineTouchData: _tooltipData(controller, color, cs), gridData: _gridData(interval, cs), titlesData: _titlesData(interval, xInterval, days, controller, cs), borderData: FlBorderData(show: false), lineBarsData: [ if (compareSpots != null && compareSpots!.isNotEmpty) _line(compareSpots!, cs.onSurfaceVariant, isDashed: true, isStep: true, width: 1.5, cs: cs), _line(spots, color, isStep: true, width: 2.5, cs: cs), ], )), ), ), ), _ControlBar(controller: controller, accentColor: color, cs: cs), ], ), ), ); } } // ─── DYNAMIC MULTI-LINE CHART CARD ────────────────────────────────────────── class _DynamicMultiLineCard extends StatelessWidget { final String title, subtitle; final IconData icon; final StaticController controller; final List Function(EmployeeChartData) getSpots; final List Function(EmployeeChartData) getCompareSpots; final int Function(EmployeeChartData) getTotal; final VoidCallback? onTap; final ColorScheme cs; const _DynamicMultiLineCard({ required this.title, required this.subtitle, required this.icon, required this.controller, required this.getSpots, required this.getCompareSpots, required this.getTotal, this.onTap, required this.cs, }); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final employees = controller.employeeData.values.toList(); employees.sort((a, b) => getTotal(b).compareTo(getTotal(a))); final allSpots = employees.expand((e) => getSpots(e)).toList(); final maxY = _maxY(allSpots); final interval = _interval(maxY); final days = _days(controller); final xInterval = _xInterval(days); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), border: Border.all(color: cs.outline), boxShadow: [ BoxShadow( color: cs.shadow.withValues(alpha: 0.08), blurRadius: 15, offset: const Offset(0, 6), ), ], ), child: Column( children: [ InkWell( onTap: onTap, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), child: Padding( padding: const EdgeInsets.all(18), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [ Container( padding: const EdgeInsets.all(9), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(10), border: Border.all( color: cs.primary.withValues(alpha: 0.20)), ), child: Icon(icon, color: cs.primary, size: 18), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w700)), Text(subtitle, style: TextStyle( color: cs.onSurfaceVariant, fontSize: 11)), if (onTap != null) Text( 'اضغط لعرض التفاصيل اليومية', style: TextStyle( color: cs.primary, fontSize: 9, fontWeight: FontWeight.w600), ), ]), ]), if (onTap != null) Icon(Icons.arrow_forward_ios_rounded, size: 14, color: cs.onSurfaceVariant), ], ), ), ), if (employees.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Text('لا توجد بيانات', style: TextStyle(color: cs.onSurfaceVariant)), ) else ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 4), child: Wrap( spacing: 10, runSpacing: 8, children: employees.map((emp) { return _LegendChip( name: emp.name.toUpperCase(), color: emp.color, total: getTotal(emp), cs: cs, ); }).toList(), ), ), const SizedBox(height: 8), SizedBox( height: 240, child: Padding( padding: const EdgeInsets.fromLTRB(4, 0, 12, 10), child: Directionality( textDirection: TextDirection.ltr, child: LineChart(LineChartData( minX: 1, maxX: days > 0 ? days : 1, minY: 0, maxY: maxY, lineTouchData: _tooltipData(controller, cs.primary, cs), gridData: _gridData(interval, cs), titlesData: _titlesData(interval, xInterval, days, controller, cs), borderData: FlBorderData(show: false), lineBarsData: [ if (controller.isComparing) ...employees.map((emp) => _line( getCompareSpots(emp), emp.color.withValues(alpha: 0.25), isDashed: true, isStep: false, width: 1.5, cs: cs, )), ...employees.map((emp) => _line( getSpots(emp), emp.color, isStep: false, width: 2.5, cs: cs, )), ], )), ), ), ), ], _ControlBar(controller: controller, accentColor: cs.primary, cs: cs), ], ), ), ); } } // ─── LEGEND CHIP ───────────────────────────────────────────────────────────── class _LegendChip extends StatelessWidget { final String name; final Color color; final int total; final ColorScheme cs; const _LegendChip( {required this.name, required this.color, required this.total, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(20), border: Border.all(color: color.withValues(alpha: 0.25)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 6), Text(name, style: TextStyle( color: color, fontSize: 10, fontWeight: FontWeight.w700)), const SizedBox(width: 5), Text('$total', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 10, fontWeight: FontWeight.w500)), ], ), ); } } // ─── EMPLOYEE LEADERBOARD ─────────────────────────────────────────────────── class _EmployeeLeaderboard extends StatelessWidget { final List stats; final ColorScheme cs; const _EmployeeLeaderboard({required this.stats, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final maxCount = stats.isEmpty ? 1 : stats.first.count; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), border: Border.all(color: cs.outline), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: cs.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(9), border: Border.all( color: cs.warning.withValues(alpha: 0.25)), ), child: Icon(Icons.emoji_events_rounded, color: cs.warning, size: 16), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('لوحة الصدارة', style: TextStyle( color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w700)), Text('إجمالي الإدخالات حسب الموظف', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 10)), ], ), ]), Text( '${stats.fold(0, (s, e) => s + e.count)} إجمالي', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.w600), ), ], ), const SizedBox(height: 20), ...List.generate(stats.length, (i) { final stat = stats[i]; final pct = maxCount > 0 ? stat.count / maxCount : 0.0; return Padding( padding: const EdgeInsets.only(bottom: 14), child: Column( children: [ Row( children: [ _RankBadge(rank: i + 1, color: stat.color, cs: cs), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( stat.name.toUpperCase(), style: TextStyle( color: stat.color, fontSize: 12, fontWeight: FontWeight.w700, ), ), Text( '${stat.count}', style: TextStyle( color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w800, fontFamily: 'monospace', ), ), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: pct.toDouble(), backgroundColor: cs.outline, valueColor: AlwaysStoppedAnimation(stat.color), minHeight: 5, ), ), ], ), ), ], ), ], ), ); }), ], ), ), ); } } class _RankBadge extends StatelessWidget { final int rank; final Color color; final ColorScheme cs; const _RankBadge({required this.rank, required this.color, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isTop3 = rank <= 3; final medalColors = [ cs.warning, cs.onSurfaceVariant, const Color(0xFFCD7F32) ]; return Container( width: 32, height: 32, decoration: BoxDecoration( color: isTop3 ? medalColors[rank - 1].withValues(alpha: 0.12) : cs.outline, shape: BoxShape.circle, border: Border.all( color: isTop3 ? medalColors[rank - 1].withValues(alpha: 0.4) : Colors.transparent), ), child: Center( child: Text( '$rank', style: TextStyle( color: isTop3 ? medalColors[rank - 1] : cs.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.w800, ), ), ), ); } } // ─── CONTROL BAR ───────────────────────────────────────────────────────────── class _ControlBar extends StatelessWidget { final StaticController controller; final Color accentColor; final ColorScheme cs; const _ControlBar( {required this.controller, required this.accentColor, required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: cs.surface, borderRadius: const BorderRadius.vertical(bottom: Radius.circular(20)), border: Border(top: BorderSide(color: cs.outline)), ), child: Row( children: [ Expanded( child: InkWell( onTap: () async { final now = DateTime.now(); final currentEnd = controller.endDate ?? now; final lastDate = currentEnd.isAfter(now) ? currentEnd.add(const Duration(days: 1)) : now.add(const Duration(days: 1)); final picked = await showDateRangePicker( context: context, firstDate: DateTime(2020), lastDate: lastDate, initialDateRange: DateTimeRange( start: controller.startDate ?? now.subtract(const Duration(days: 30)), end: currentEnd, ), builder: (ctx, child) => Theme( data: Theme.of(ctx).copyWith( colorScheme: Theme.of(ctx).colorScheme.copyWith( primary: accentColor, surface: cs.surfaceContainerHighest, ), ), child: child!, ), ); if (picked != null) { controller.updateDateRange(picked.start, picked.end); } }, borderRadius: BorderRadius.circular(9), child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(9), border: Border.all(color: cs.outline), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.calendar_today_rounded, size: 13, color: accentColor), const SizedBox(width: 7), Text( _formatRange(controller), style: TextStyle( color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ], ), ), ), ), const SizedBox(width: 10), InkWell( onTap: controller.toggleComparison, borderRadius: BorderRadius.circular(9), child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: BoxDecoration( color: controller.isComparing ? cs.error.withValues(alpha: 0.10) : cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(9), border: Border.all( color: controller.isComparing ? cs.error.withValues(alpha: 0.3) : cs.outline), ), child: Row( children: [ Icon( controller.isComparing ? Icons.visibility_off_outlined : Icons.compare_arrows_rounded, size: 14, color: controller.isComparing ? cs.error : accentColor, ), const SizedBox(width: 5), Text( controller.isComparing ? 'إلغاء' : 'مقارنة', style: TextStyle( color: controller.isComparing ? cs.error : accentColor, fontSize: 11, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ), ); } String _formatRange(StaticController c) { if (c.startDate == null || c.endDate == null) return c.currentDateString; return "${c.startDate!.day}/${c.startDate!.month}/${c.startDate!.year}" " - ${c.endDate!.day}/${c.endDate!.month}/${c.endDate!.year}"; } } // ─── LOADING STATE ─────────────────────────────────────────────────────────── class _LoadingState extends StatelessWidget { final ColorScheme cs; const _LoadingState({required this.cs}); @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 40, height: 40, child: CircularProgressIndicator( color: cs.primary, strokeWidth: 2, backgroundColor: cs.primary.withValues(alpha: 0.1), ), ), const SizedBox(height: 16), Text('جاري تحميل الإحصائيات...', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), ], ), ); } } // ─── CHART HELPERS (World-class premium) ───────────────────────────────────── LineChartBarData _line( List spots, Color color, { bool isDashed = false, bool isStep = false, double width = 2.5, required ColorScheme cs, }) { return LineChartBarData( spots: spots, isCurved: !isStep, curveSmoothness: 0.4, isStepLineChart: isStep, color: color, barWidth: width + 1, isStrokeCapRound: !isStep, shadow: Shadow( color: color.withValues(alpha: 0.4), blurRadius: 8, offset: const Offset(0, 4)), dotData: const FlDotData(show: false), dashArray: isDashed ? [5, 5] : null, lineChartStepData: const LineChartStepData(stepDirection: 0.5), belowBarData: BarAreaData( show: !isDashed, gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ color.withValues(alpha: 0.35), color.withValues(alpha: 0.0), ], ), ), ); } LineTouchData _tooltipData( StaticController c, Color accentColor, ColorScheme cs) { return LineTouchData( handleBuiltInTouches: true, touchTooltipData: LineTouchTooltipData( getTooltipColor: (_) => cs.surfaceContainerHighest, tooltipBorder: BorderSide(color: cs.outline), tooltipPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), tooltipRoundedRadius: 10, getTooltipItems: (spots) { return spots.map((barSpot) { final start = c.startDate; String dateStr = ''; if (start != null) { final date = start.add(Duration(days: barSpot.x.toInt() - 1)); dateStr = DateFormat('d MMM', 'en').format(date); } return LineTooltipItem( '$dateStr\n', TextStyle( color: cs.onSurfaceVariant, fontWeight: FontWeight.w600, fontSize: 11), children: [ TextSpan( text: barSpot.y.toInt().toString(), style: TextStyle( color: barSpot.bar.color, fontWeight: FontWeight.w900, fontSize: 15), ), ], ); }).toList(); }, ), ); } FlGridData _gridData(double interval, ColorScheme cs) { return FlGridData( show: true, drawVerticalLine: false, horizontalInterval: interval, getDrawingHorizontalLine: (_) => FlLine( color: cs.outline.withValues(alpha: 0.5), strokeWidth: 1), ); } FlTitlesData _titlesData( double interval, double xInterval, double days, StaticController c, ColorScheme cs, ) { return FlTitlesData( show: true, rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, interval: xInterval, getTitlesWidget: (value, _) { if (value <= 0 || value > days) return const SizedBox.shrink(); if (c.startDate == null) return const SizedBox.shrink(); final date = c.startDate!.add(Duration(days: value.toInt() - 1)); return Padding( padding: const EdgeInsets.only(top: 6), child: Text( DateFormat('d/M', 'en').format(date), style: TextStyle( color: cs.onSurfaceVariant, fontSize: 9, fontWeight: FontWeight.w600), ), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: interval, reservedSize: 36, getTitlesWidget: (value, _) => Text( _formatNum(value), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 9), ), ), ), ); } double _maxY(List spots) { if (spots.isEmpty) return 10; final max = spots.map((s) => s.y).reduce((a, b) => a > b ? a : b); return max <= 0 ? 5 : (max * 1.2).ceilToDouble(); } double _interval(double maxY) { if (maxY <= 10) return 2; if (maxY <= 50) return 10; if (maxY <= 100) return 20; if (maxY <= 500) return 100; if (maxY <= 1000) return 200; return maxY / 5; } double _days(StaticController c) { if (c.startDate == null || c.endDate == null) return 30; return c.endDate!.difference(c.startDate!).inDays + 1.0; } double _xInterval(double days) { if (days > 60) return 30; if (days > 30) return 10; if (days > 10) return 5; return 1; } String _formatNum(double v) { if (v >= 1000) return '${(v / 1000).toStringAsFixed(1)}k'; return v.toInt().toString(); }