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:sefer_admin1/constant/colors.dart'; import 'package:sefer_admin1/controller/admin/static_controller.dart'; import 'package:sefer_admin1/views/widgets/mycircular.dart'; // import صفحة الملاحظات الجديدة import 'notes_driver_page.dart'; class StaticDash extends StatelessWidget { const StaticDash({super.key}); @override Widget build(BuildContext context) { final controller = Get.put(StaticController()); bool isRtl = Directionality.of(context) == TextDirection.rtl; return Scaffold( backgroundColor: const Color(0xFFF0F2F5), appBar: AppBar( title: Text( 'لوحة الإحصائيات'.tr, style: const TextStyle( color: Color(0xFF1A1A1A), fontWeight: FontWeight.w800, fontSize: 22, ), ), backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, iconTheme: const IconThemeData(color: Colors.black87), leading: IconButton( onPressed: () => Get.back(), icon: Icon( isRtl ? Icons.arrow_forward_ios : Icons.arrow_back_ios, size: 20, )), actions: [ Container( margin: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4)) ], ), child: IconButton( onPressed: () async { await controller.getAll(); }, icon: const Icon(Icons.refresh_rounded, color: AppColor.primaryColor), ), ), ], ), body: GetBuilder( builder: (staticController) { if (staticController.isLoading) { return const Center(child: MyCircularProgressIndicator()); } return AnimationLimiter( child: ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), physics: const BouncingScrollPhysics(), children: AnimationConfiguration.toStaggeredList( duration: const Duration(milliseconds: 500), childAnimationBuilder: (widget) => SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation(child: widget), ), children: [ Center( child: Padding( padding: const EdgeInsets.only(bottom: 10), child: Text( _formatDateRange(staticController), style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold, fontSize: 14), ), ), ), _buildSectionHeader("نظرة عامة على النمو", Icons.trending_up), // --- Passengers Card --- _buildModernChartCard( context, title: 'الركاب', totalValue: staticController.totalMonthlyPassengers.toString(), spots: staticController.chartDataPassengers .cast() .toList(), compareSpots: staticController.isComparing ? staticController.chartDataPassengersCompare .cast() .toList() : null, baseColor: const Color(0xFF2979FF), icon: Icons.groups_rounded, controller: staticController, ), const SizedBox(height: 16), // --- Drivers Card --- _buildModernChartCard( context, title: 'السائقون', totalValue: staticController.totalMonthlyDrivers.toString(), spots: staticController.chartDataDrivers .cast() .toList(), compareSpots: staticController.isComparing ? staticController.chartDataDriversCompare .cast() .toList() : null, baseColor: const Color(0xFFFF9100), icon: Icons.drive_eta_rounded, controller: staticController, ), const SizedBox(height: 16), // --- Rides Card --- _buildModernChartCard( context, title: 'الرحلات', totalValue: staticController.totalMonthlyRides.toString(), spots: staticController.chartDataRides.cast().toList(), compareSpots: staticController.isComparing ? staticController.chartDataRidesCompare .cast() .toList() : null, baseColor: const Color(0xFF651FFF), icon: Icons.map_rounded, controller: staticController, ), const SizedBox(height: 24), _buildSectionHeader( "أداء فريق العمل", Icons.workspaces_filled), // --- Activations Card (No Click Action) --- _buildMultiLineChartCard( context: context, title: 'تفعيل السائقين (Activations)', spotsrama1: staticController.chartDataEmployeerama1, spotsShahd: staticController.chartDataEmployeeshahd, spotsRama: staticController.chartDataEmployeeRama2, spotsMayar: staticController.chartDataEmployeeSefer4, spotsrama1Compare: staticController.chartDataEmployeerama1Compare, spotsShahdCompare: staticController.chartDataEmployeeshahdCompare, spotsRamaCompare: staticController.chartDataEmployeeRama2Compare, spotsMayarCompare: staticController.chartDataEmployeeSefer4Compare, staticController: staticController, ), const SizedBox(height: 16), // --- 🔴 Modified: Calls Card (With Navigation) --- _buildMultiLineChartCard( context: context, title: 'عدد المكالمات (Calls)', // إضافة التوجيه للصفحة الجديدة عند الضغط onTap: () { Get.to(() => const DailyNotesView()); }, spotsrama1: staticController.chartDataCallsrama1, spotsShahd: staticController.chartDataCallsShahd, spotsRama: staticController.chartDataCallsRama2, spotsMayar: staticController.chartDataCallsSefer4, spotsrama1Compare: staticController.chartDataCallsrama1Compare, spotsShahdCompare: staticController.chartDataCallsShahdCompare, spotsRamaCompare: staticController.chartDataCallsRama2Compare, spotsMayarCompare: staticController.chartDataCallsSefer4Compare, staticController: staticController, ), const SizedBox(height: 24), // --- Employment Stats List --- if (staticController.employmentStatsList.isNotEmpty) ...[ _buildSectionHeader( "إجمالي الإدخالات حسب الموظف", Icons.list_alt_rounded), _buildEmploymentStatsList( staticController.employmentStatsList), const SizedBox(height: 24), ], _buildSectionHeader( "متابعة التسجيل", Icons.assignment_turned_in), // --- Drivers Matching Notes Card --- _buildModernChartCard( context, title: 'سائقين بعد الاتصال', totalValue: staticController.staticList.isNotEmpty && staticController.staticList[0] ['totalMonthlyMatchingNotes'] != null ? "${staticController.staticList[0]['totalMonthlyMatchingNotes']}" : "0", spots: staticController.chartDataDriversMatchingNotes .cast() .toList(), compareSpots: staticController.isComparing ? staticController.chartDataDriversMatchingNotesCompare .cast() .toList() : null, baseColor: const Color(0xFF00BFA5), icon: Icons.phone_in_talk_rounded, controller: staticController, ), const SizedBox(height: 40), ], ), ), ); }, ), ); } // ... (Employment Stats List - No Change) Widget _buildEmploymentStatsList(List> stats) { Color getEmployeeColor(String name) { String n = name.toLowerCase().trim(); if (n.contains('shahd')) return Colors.redAccent; if (n.contains('mayar')) return Colors.amber.shade700; if (n.contains('rama2')) return Colors.green; if (n.contains('rama1')) return Colors.blue; return Colors.blueGrey; } return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 15, offset: const Offset(0, 5)) ]), padding: const EdgeInsets.all(20), child: Column( children: [ Row( children: [ Icon(Icons.people_outline, color: Colors.blueGrey.shade700), const SizedBox(width: 10), Text("الأداء الإجمالي (العدد)", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.grey.shade800)), ], ), const Divider(height: 25), Wrap( spacing: 12, runSpacing: 12, children: stats.map((item) { Color itemColor = getEmployeeColor(item['name'].toString()); return Container( width: 100, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), decoration: BoxDecoration( color: itemColor.withOpacity(0.08), borderRadius: BorderRadius.circular(12), border: Border.all( color: itemColor.withOpacity(0.5), width: 1.5)), child: Column( children: [ Text( item['name'].toString().toUpperCase(), style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: itemColor), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), Text( item['count'].toString(), style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: Colors.grey.shade800), ) ], ), ); }).toList(), ), ], ), ); } // ... (Header Widget - No Change) Widget _buildSectionHeader(String title, IconData icon) { return Padding( padding: const EdgeInsets.only(bottom: 12.0, top: 8.0, right: 4), child: Row( children: [ Icon(icon, size: 20, color: Colors.grey[600]), const SizedBox(width: 8), Text(title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey[800], fontFamily: 'Cairo')), ], ), ); } // ... (Modern Chart Card - No Change) Widget _buildModernChartCard(BuildContext context, {required String title, required String totalValue, required List spots, List? compareSpots, required Color baseColor, required IconData icon, required StaticController controller}) { List allSpots = [...spots]; if (compareSpots != null) allSpots.addAll(compareSpots); double maxY = _calculateMaxY(allSpots); double interval = _calculateInterval(maxY); double daysInPeriod = _getDaysInPeriod(controller); double xInterval = _calculateXInterval(daysInPeriod); return Container( height: 400, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: baseColor.withOpacity(0.08), blurRadius: 20, offset: const Offset(0, 8)) ]), child: Column( children: [ _buildCardHeader(context, title, totalValue, icon, baseColor, controller.isComparing), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 10), child: Directionality( textDirection: TextDirection.ltr, child: LineChart(LineChartData( minX: 1, maxX: daysInPeriod > 0 ? daysInPeriod : 1, minY: 0, maxY: maxY, lineTouchData: _buildTooltipData(controller), gridData: _buildGridData(interval), titlesData: _buildTitlesData( interval, xInterval, daysInPeriod, controller), borderData: FlBorderData(show: false), lineBarsData: [ if (compareSpots != null && compareSpots.isNotEmpty) _buildLine(compareSpots, Colors.grey.withOpacity(0.4), isDashed: true, isStep: true), _buildLine(spots, baseColor, isStep: true), ])), ), ), ), _buildControlBar(context, controller, baseColor), ], ), ); } // --- 🔴 Modified: Multi Line Chart (Adding onTap) --- Widget _buildMultiLineChartCard({ required BuildContext context, required String title, required StaticController staticController, // Add onTap parameter VoidCallback? onTap, required List spotsrama1, required List spotsShahd, required List spotsRama, required List spotsMayar, required List spotsrama1Compare, required List spotsShahdCompare, required List spotsRamaCompare, required List spotsMayarCompare, }) { final allSpots = [ ...spotsrama1, ...spotsShahd, ...spotsRama, ...spotsMayar ]; double maxY = _calculateMaxY(allSpots); double interval = _calculateInterval(maxY); double daysInPeriod = _getDaysInPeriod(staticController); double xInterval = _calculateXInterval(daysInPeriod); return Container( height: 460, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 15, offset: const Offset(0, 5)) ]), child: Column( children: [ // 🔴 Wrap Header with InkWell if onTap is provided InkWell( onTap: onTap, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), child: _buildCardHeader( context, title, null, Icons.bar_chart_rounded, Colors.black54, staticController.isComparing, showArrow: onTap != null), // Pass showArrow flag ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Wrap( spacing: 16, runSpacing: 8, alignment: WrapAlignment.center, children: [ _buildLegendItem("راما 1", Colors.blue), _buildLegendItem("شهد", Colors.redAccent), _buildLegendItem("راما 2", Colors.green), _buildLegendItem("ميار", Colors.amber.shade700), ], ), ), const SizedBox(height: 15), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 10), child: Directionality( textDirection: TextDirection.ltr, child: LineChart(LineChartData( minX: 1, maxX: daysInPeriod > 0 ? daysInPeriod : 1, minY: 0, maxY: maxY, lineTouchData: _buildTooltipData(staticController), gridData: _buildGridData(interval), titlesData: _buildTitlesData( interval, xInterval, daysInPeriod, staticController), borderData: FlBorderData(show: false), lineBarsData: [ if (staticController.isComparing) ...[ _buildLine( spotsrama1Compare, Colors.blue.withOpacity(0.3), isDashed: true, isStep: false), _buildLine(spotsShahdCompare, Colors.redAccent.withOpacity(0.3), isDashed: true, isStep: false), _buildLine( spotsRamaCompare, Colors.green.withOpacity(0.3), isDashed: true, isStep: false), _buildLine(spotsMayarCompare, Colors.amber.shade700.withOpacity(0.3), isDashed: true, isStep: false), ], _buildLine(spotsrama1, Colors.blue, isStep: false), _buildLine(spotsShahd, Colors.redAccent, isStep: false), _buildLine(spotsRama, Colors.green, isStep: false), _buildLine(spotsMayar, Colors.amber.shade700, isStep: false), ])), ), ), ), _buildControlBar(context, staticController, Colors.blueGrey), ], ), ); } // ... (Shared Components) ... // 🔴 Modified _buildCardHeader to accept showArrow Widget _buildCardHeader(BuildContext context, String title, String? totalValue, IconData icon, Color color, bool isComparing, {bool showArrow = false}) { return Padding( padding: const EdgeInsets.all(20.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(16)), child: Icon(icon, color: color, size: 26)), const SizedBox(width: 14), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey[800])), if (totalValue != null) ...[ const SizedBox(height: 4), Text('الإجمالي: $totalValue', style: TextStyle( fontSize: 13, color: Colors.grey[500], fontWeight: FontWeight.w600)) ], // إضافة نص صغير يدل على إمكانية الضغط if (showArrow) Padding( padding: const EdgeInsets.only(top: 4.0), child: Text("اضغط لعرض التفاصيل اليومية", style: TextStyle( fontSize: 10, color: AppColor.primaryColor)), ) ]), ], ), Row(children: [ if (showArrow) Icon(Icons.arrow_forward_ios_rounded, size: 16, color: Colors.grey[400]), if (isComparing) ...[ const SizedBox(width: 8), Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.grey, shape: BoxShape.circle)), const SizedBox(width: 4), Text("الفترة السابقة", style: TextStyle(fontSize: 10, color: Colors.grey[600])) ] ]) ], ), ); } LineTouchData _buildTooltipData(StaticController controller) { return LineTouchData( handleBuiltInTouches: true, touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => Colors.black87, tooltipPadding: const EdgeInsets.all(10), tooltipRoundedRadius: 8, getTooltipItems: (List touchedBarSpots) { return touchedBarSpots.map((barSpot) { DateTime? start = controller.startDate; if (start != null) { DateTime date = start.add(Duration(days: barSpot.x.toInt() - 1)); String formattedDate = DateFormat('d MMM', 'en').format(date); return LineTooltipItem( '$formattedDate \n', const TextStyle( color: Colors.white70, fontWeight: FontWeight.bold, fontSize: 12), children: [ TextSpan( text: barSpot.y.toInt().toString(), style: TextStyle( color: barSpot.bar.color, fontWeight: FontWeight.w900, fontSize: 14)) ]); } return LineTooltipItem( barSpot.y.toString(), const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)); }).toList(); }, ), ); } FlGridData _buildGridData(double interval) { return FlGridData( show: true, drawVerticalLine: false, horizontalInterval: interval, getDrawingHorizontalLine: (value) => FlLine(color: Colors.grey.withOpacity(0.08), strokeWidth: 1)); } FlTitlesData _buildTitlesData(double interval, double xInterval, double daysInPeriod, StaticController controller) { 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: 30, interval: xInterval, getTitlesWidget: (value, meta) { if (value <= 0 || value > daysInPeriod) return const SizedBox.shrink(); if (controller.startDate != null) { int dayOffset = value.toInt() - 1; DateTime date = controller.startDate!.add(Duration(days: dayOffset)); String text = DateFormat('d/M', 'en').format(date); return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text(text, style: TextStyle( color: Colors.grey[400], fontSize: 10, fontWeight: FontWeight.bold))); } return const SizedBox.shrink(); })), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: interval, reservedSize: 40, getTitlesWidget: (value, meta) => Text(_formatNumber(value), style: TextStyle(color: Colors.grey[400], fontSize: 10)))), ); } Widget _buildControlBar( BuildContext context, StaticController controller, Color color) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)), border: Border(top: BorderSide(color: Colors.grey.shade200))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: InkWell( onTap: () async { DateTimeRange? picked = await showDateRangePicker( context: context, firstDate: DateTime(2020), lastDate: DateTime.now().add(const Duration(days: 1)), initialDateRange: DateTimeRange( start: controller.startDate ?? DateTime.now() .subtract(const Duration(days: 30)), end: controller.endDate ?? DateTime.now()), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: AppColor.primaryColor, onPrimary: Colors.white, onSurface: Colors.black)), child: child!); }); if (picked != null) controller.updateDateRange(picked.start, picked.end); }, child: Container( padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), color: Colors.white), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.calendar_today_rounded, size: 16, color: color), const SizedBox(width: 8), Text(_formatDateRange(controller), style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.grey[800]), overflow: TextOverflow.ellipsis) ])))), const SizedBox(width: 12), TextButton.icon( onPressed: controller.toggleComparison, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8))), icon: Icon( controller.isComparing ? Icons.visibility_off_outlined : Icons.compare_arrows_rounded, size: 18, color: controller.isComparing ? Colors.redAccent : color), label: Text(controller.isComparing ? "إلغاء" : "مقارنة", style: TextStyle( fontSize: 12, color: controller.isComparing ? Colors.redAccent : color, fontWeight: FontWeight.w600))) ], ), ); } LineChartBarData _buildLine(List spots, Color color, {bool isDashed = false, bool isStep = false}) { return LineChartBarData( spots: spots, isCurved: !isStep, // Curved if not step curveSmoothness: 0.25, isStepLineChart: isStep, color: color, barWidth: isDashed ? 2 : 2.5, isStrokeCapRound: !isStep, dotData: const FlDotData(show: false), dashArray: isDashed ? [5, 5] : null, lineChartStepData: const LineChartStepData(stepDirection: 0.5), ); } Widget _buildLegendItem(String name, Color color) { return Row(mainAxisSize: MainAxisSize.min, children: [ Container( width: 10, height: 10, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 6), Text(name, style: TextStyle( fontSize: 12, color: Colors.grey[700], fontWeight: FontWeight.w600)) ]); } double _getDaysInPeriod(StaticController controller) { if (controller.startDate == null || controller.endDate == null) return 30; return controller.endDate!.difference(controller.startDate!).inDays + 1.0; } double _calculateXInterval(double daysInPeriod) { if (daysInPeriod > 60) return 30; if (daysInPeriod > 30) return 10; if (daysInPeriod > 10) return 5; return 1; } String _formatDateRange(StaticController controller) { if (controller.startDate == null || controller.endDate == null) return controller.currentDateString; String start = "${controller.startDate!.day}/${controller.startDate!.month}/${controller.startDate!.year}"; String end = "${controller.endDate!.day}/${controller.endDate!.month}/${controller.endDate!.year}"; return "$start - $end"; } double _calculateMaxY(List spots) { if (spots.isEmpty) return 10; double maxVal = 0; for (var spot in spots) { if (spot.y > maxVal) maxVal = spot.y; } if (maxVal == 0) return 5; return (maxVal * 1.2).ceilToDouble(); } double _calculateInterval(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; } String _formatNumber(double value) { if (value >= 1000) return '${(value / 1000).toStringAsFixed(1)}k'; return value.toInt().toString(); } }