Files
Siro/siro_admin/lib/views/admin/static/advanced_analytics_page.dart
2026-06-09 08:40:31 +03:00

324 lines
11 KiB
Dart

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/analytics_v2_controller.dart';
import 'package:intl/intl.dart';
class AdvancedAnalyticsPage extends StatelessWidget {
const AdvancedAnalyticsPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(AnalyticsV2Controller());
return Scaffold(
backgroundColor: AppColor.bg,
appBar: AppBar(
title: const Text('التحليلات المتقدمة',
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: AppColor.surface,
elevation: 0,
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () => controller.fetchAllAnalytics(),
)
],
),
body: GetBuilder<AnalyticsV2Controller>(
builder: (ctrl) {
if (ctrl.isLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColor.accent));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSummarySection(ctrl.revenueData['summary']),
const SizedBox(height: 24),
_buildSectionTitle('إيرادات آخر 30 يوم'),
_buildRevenueChart(ctrl.revenueData['daily'] ?? []),
const SizedBox(height: 32),
_buildSectionTitle('نمو المستخدمين (آخر 30 يوم)'),
_buildGrowthChart(ctrl.growthData),
const SizedBox(height: 32),
_buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)'),
_buildTopDriversList(ctrl.topDrivers),
const SizedBox(height: 40),
],
),
);
},
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
title,
style: const TextStyle(
color: AppColor.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildSummarySection(Map<String, dynamic>? summary) {
if (summary == null) return const SizedBox();
return Row(
children: [
_buildSummaryCard('إجمالي الإيرادات',
'${summary['total_revenue_all'] ?? 0}', AppColor.info),
const SizedBox(width: 12),
_buildSummaryCard('صافي الربح', '${summary['total_profit_all'] ?? 0}',
AppColor.success),
],
);
}
Widget _buildSummaryCard(String title, String value, Color color) {
return Expanded(
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
const SizedBox(height: 8),
Text(value,
style: TextStyle(
color: color, fontSize: 22, fontWeight: FontWeight.bold)),
],
),
),
);
}
Widget _buildRevenueChart(List<dynamic> daily) {
if (daily.isEmpty) return const Center(child: Text('لا توجد بيانات'));
List<FlSpot> revenueSpots = [];
List<FlSpot> profitSpots = [];
for (int i = 0; i < daily.length; i++) {
revenueSpots.add(FlSpot(i.toDouble(),
double.tryParse(daily[i]['total_revenue'].toString()) ?? 0));
profitSpots.add(FlSpot(i.toDouble(),
double.tryParse(daily[i]['company_profit'].toString()) ?? 0));
}
return Container(
height: 300,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(24),
),
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (v) =>
FlLine(color: AppColor.divider, strokeWidth: 1)),
titlesData: FlTitlesData(
show: true,
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 && val.toInt() < daily.length) {
return Text(
daily[val.toInt()]['date'].toString().substring(8),
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 10));
}
return const SizedBox();
},
),
),
),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: revenueSpots,
isCurved: true,
color: AppColor.info,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true, color: AppColor.info.withOpacity(0.1)),
),
LineChartBarData(
spots: profitSpots,
isCurved: true,
color: AppColor.success,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true, color: AppColor.success.withOpacity(0.1)),
),
],
),
),
);
}
Widget _buildGrowthChart(Map<String, dynamic> data) {
final passengers = data['passenger_daily'] as List<dynamic>? ?? [];
final drivers = data['driver_daily'] as List<dynamic>? ?? [];
if (passengers.isEmpty && drivers.isEmpty)
return const Center(child: Text('لا توجد بيانات'));
List<BarChartGroupData> barGroups = [];
int maxLength =
passengers.length > drivers.length ? passengers.length : drivers.length;
for (int i = 0; i < maxLength; i++) {
double pCount = i < passengers.length
? double.tryParse(passengers[i]['new_passengers'].toString()) ?? 0
: 0;
double dCount = i < drivers.length
? double.tryParse(drivers[i]['new_drivers'].toString()) ?? 0
: 0;
barGroups.add(
BarChartGroupData(
x: i,
barRods: [
BarChartRodData(
toY: pCount,
color: AppColor.info,
width: 8,
borderRadius: BorderRadius.circular(4)),
BarChartRodData(
toY: dCount,
color: AppColor.warning,
width: 8,
borderRadius: BorderRadius.circular(4)),
],
),
);
}
return Container(
height: 250,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(24),
),
child: BarChart(
BarChartData(
barGroups: barGroups,
borderData: FlBorderData(show: false),
titlesData: FlTitlesData(
show: true,
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 && val.toInt() < passengers.length) {
return Text(
passengers[val.toInt()]['date'].toString().substring(8),
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 10));
}
return const SizedBox();
},
),
),
),
),
),
);
}
Widget _buildTopDriversList(List<dynamic> drivers) {
if (drivers.isEmpty) return const Center(child: Text('لا توجد بيانات'));
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: drivers.length,
itemBuilder: (ctx, i) {
final d = drivers[i];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
CircleAvatar(
backgroundColor: AppColor.accent.withOpacity(0.1),
child: Text('${i + 1}',
style: const TextStyle(
color: AppColor.accent, fontWeight: FontWeight.bold)),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${d['first_name']} ${d['last_name']}',
style: const TextStyle(
color: AppColor.textPrimary,
fontWeight: FontWeight.bold)),
Text(d['phone'] ?? '',
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('${d['completed_rides']} رحلة',
style: const TextStyle(
color: AppColor.info, fontWeight: FontWeight.bold)),
Text('${d['total_revenue']} ل.س',
style: const TextStyle(
color: AppColor.success,
fontSize: 12,
fontWeight: FontWeight.bold)),
],
),
],
),
);
},
);
}
}