Update: 2026-08-02 22:51:18
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:siro_admin/constant/theme.dart';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
@@ -502,6 +503,167 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
/// توزيع الرحلات على فترات اليوم (صباح / مساء / ليل).
|
||||
/// الحدود مطابقة لما يحسبه backend/Admin/dashbord.php:
|
||||
/// صباحاً 6–11، مساءً 12–17، ليلاً 18–5.
|
||||
Widget _buildPeakHoursCard(
|
||||
double morning, double evening, double night, ColorScheme cs) {
|
||||
final total = morning + evening + night;
|
||||
final maxVal = [morning, evening, night].reduce((a, b) => a > b ? a : b);
|
||||
final maxY = maxVal > 0 ? (maxVal * 1.3).ceilToDouble() : 10.0;
|
||||
|
||||
final slots = [
|
||||
(label: 'صباحاً', hint: '6–11', value: morning, color: cs.warning),
|
||||
(label: 'مساءً', hint: '12–17', value: evening, color: cs.info),
|
||||
(label: 'ليلاً', hint: '18–5', value: night, color: cs.secondary),
|
||||
];
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: 1,
|
||||
duration: const Duration(milliseconds: 450),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 40.0,
|
||||
child: FadeInAnimation(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: cs.outline),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.schedule_rounded, color: cs.primary, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text('توزيع الرحلات حسب الوقت',
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
if (total > 0)
|
||||
Text('${total.toInt()} رحلة',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant, fontSize: 11)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 140,
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: maxY,
|
||||
barTouchData: BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (_) => cs.surfaceContainerHighest,
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
final pct = total > 0
|
||||
? (rod.toY / total * 100).toStringAsFixed(1)
|
||||
: '0.0';
|
||||
return BarTooltipItem(
|
||||
'${rod.toY.toInt()} ($pct%)',
|
||||
TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
getDrawingHorizontalLine: (v) => FlLine(
|
||||
color: cs.outline.withValues(alpha: 0.5),
|
||||
strokeWidth: 1),
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false)),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false)),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 34,
|
||||
getTitlesWidget: (v, _) => Text(
|
||||
v >= 1000
|
||||
? '${(v / 1000).toStringAsFixed(0)}k'
|
||||
: v.toInt().toString(),
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant, fontSize: 9),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 34,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final i = value.toInt();
|
||||
if (i < 0 || i >= slots.length) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(slots[i].label,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600)),
|
||||
Text(slots[i].hint,
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 8)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
barGroups: [
|
||||
for (var i = 0; i < slots.length; i++)
|
||||
BarChartGroupData(
|
||||
x: i,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: slots[i].value,
|
||||
color: slots[i].color,
|
||||
width: 26,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
backDrawRodData: BackgroundBarChartRodData(
|
||||
show: true,
|
||||
toY: maxY,
|
||||
color: cs.outline.withValues(alpha: 0.25),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChartsSection(
|
||||
dynamic data, DashboardController controller, ColorScheme cs) {
|
||||
final completed = (data['completed_rides'] ?? 0).toDouble();
|
||||
@@ -520,6 +682,12 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
final driverCount = (data['countDriver'] ?? 0).toDouble();
|
||||
final monthRides = (data['countRideThisMonth'] ?? 0).toDouble();
|
||||
|
||||
// توزيع الرحلات على أوقات اليوم — تعيده dashbord.php منذ البداية
|
||||
// (morning/evening/night_ride_count) ولم يكن معروضاً في أي شاشة.
|
||||
final morningRides = (data['morning_ride_count'] ?? 0).toDouble();
|
||||
final eveningRides = (data['evening_ride_count'] ?? 0).toDouble();
|
||||
final nightRides = (data['night_ride_count'] ?? 0).toDouble();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
@@ -677,7 +845,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: speed,
|
||||
color: const Color(0xFFF59E0B),
|
||||
color: cs.warning,
|
||||
width: 28,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
|
||||
),
|
||||
@@ -828,6 +996,8 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildPeakHoursCard(morningRides, eveningRides, nightRides, cs),
|
||||
const SizedBox(height: 12),
|
||||
AnimationConfiguration.staggeredList(
|
||||
position: 2,
|
||||
duration: const Duration(milliseconds: 450),
|
||||
@@ -1139,15 +1309,16 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
}
|
||||
|
||||
List<ActionCategory> _getAllActionCategories() {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return [
|
||||
ActionCategory(
|
||||
title: 'المستخدمين',
|
||||
items: [
|
||||
ActionItem('الركاب', Icons.people_outline_rounded, const Color(0xFF3B82F6),
|
||||
ActionItem('الركاب', Icons.people_outline_rounded, cs.info,
|
||||
() => Get.to(() => Passengrs())),
|
||||
ActionItem('السائقون', Icons.drive_eta_rounded, const Color(0xFFF59E0B),
|
||||
ActionItem('السائقون', Icons.drive_eta_rounded, cs.warning,
|
||||
() => Get.to(() => CaptainsPage())),
|
||||
ActionItem('المراقب', Icons.track_changes_rounded, const Color(0xFFEF4444),
|
||||
ActionItem('المراقب', Icons.track_changes_rounded, cs.danger,
|
||||
() => Get.to(() => SiroTrackerScreen())),
|
||||
],
|
||||
),
|
||||
@@ -1163,11 +1334,11 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
items: [
|
||||
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1),
|
||||
() => Get.toNamed('/promo')),
|
||||
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, const Color(0xFFF59E0B),
|
||||
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, cs.warning,
|
||||
() => Get.toNamed('/kazan')),
|
||||
ActionItem('الشكاوى', Icons.report_problem_rounded, const Color(0xFFEF4444),
|
||||
ActionItem('الشكاوى', Icons.report_problem_rounded, cs.danger,
|
||||
() => Get.toNamed('/complaints')),
|
||||
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, const Color(0xFF3B82F6),
|
||||
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, cs.info,
|
||||
() => Get.toNamed('/driver-docs')),
|
||||
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
|
||||
() => Get.toNamed('/social-intelligence')),
|
||||
@@ -1188,7 +1359,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
await Get.put(StaticController()).getAll();
|
||||
Get.to(() => const StaticDash());
|
||||
}),
|
||||
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, const Color(0xFF3B82F6),
|
||||
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, cs.info,
|
||||
() => Get.to(() => const AdvancedAnalyticsPage())),
|
||||
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
|
||||
const Color(0xFF00CEC9),
|
||||
@@ -1198,7 +1369,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
ActionCategory(
|
||||
title: 'الجودة والدعم',
|
||||
items: [
|
||||
ActionItem('القائمة السوداء', Icons.block_flipped, const Color(0xFFEF4444),
|
||||
ActionItem('القائمة السوداء', Icons.block_flipped, cs.danger,
|
||||
() => Get.to(() => const BlacklistPage())),
|
||||
],
|
||||
),
|
||||
@@ -1210,10 +1381,10 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
const Color(0xFF6366F1), () => Get.to(() => const FinancialV2Page())),
|
||||
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, const Color(0xFF6366F1),
|
||||
() => Get.to(() => Wallet())),
|
||||
ActionItem('هدية 300', Icons.card_giftcard_rounded, const Color(0xFFF59E0B),
|
||||
ActionItem('هدية 300', Icons.card_giftcard_rounded, cs.warning,
|
||||
() => Get.to(() => DriverGiftCheckPage())),
|
||||
ActionItem('الفواتير', Icons.receipt_long_rounded,
|
||||
const Color(0xFF10B981), () => Get.to(() => InvoiceListPage())),
|
||||
cs.success, () => Get.to(() => InvoiceListPage())),
|
||||
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF),
|
||||
() => Get.to(() => EmployeePage())),
|
||||
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1),
|
||||
@@ -1225,11 +1396,11 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
title: 'النظام والتواصل',
|
||||
items: [
|
||||
ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded,
|
||||
const Color(0xFFEF4444), () => Get.to(() => const AuditLogsPage())),
|
||||
cs.danger, () => Get.to(() => const AuditLogsPage())),
|
||||
ActionItem('واتساب جماعي', Icons.message_rounded,
|
||||
const Color(0xFF25D366), () => _showWhatsAppDialog(context)),
|
||||
ActionItem('أتمتة التسويق', Icons.campaign_rounded,
|
||||
const Color(0xFF10B981), () => Get.toNamed('/marketing')),
|
||||
cs.success, () => Get.toNamed('/marketing')),
|
||||
ActionItem(
|
||||
'إشعار سائقين',
|
||||
Icons.notifications_active_rounded,
|
||||
@@ -1242,7 +1413,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
const Color(0xFFF06292),
|
||||
() => Get.put(NotificationController())
|
||||
.sendNotificationPassengers()),
|
||||
ActionItem('تسجيل سائق', Icons.person_add_rounded, const Color(0xFF3B82F6),
|
||||
ActionItem('تسجيل سائق', Icons.person_add_rounded, cs.info,
|
||||
() => Get.to(() => DriversPendingPage())),
|
||||
ActionItem(
|
||||
'تحديث التطبيق',
|
||||
@@ -1251,7 +1422,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
() => Get.to(() => PackageUpdateScreen())),
|
||||
ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1),
|
||||
() => Get.to(() => ServerMonitorPage())),
|
||||
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, const Color(0xFFEF4444),
|
||||
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, cs.danger,
|
||||
() => Get.to(() => ErrorListPage())),
|
||||
ActionItem(
|
||||
'أداة التشفير',
|
||||
@@ -1275,7 +1446,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
ActionItem(
|
||||
'إضافة خدمة عملاء',
|
||||
Icons.support_agent_rounded,
|
||||
const Color(0xFF3B82F6),
|
||||
cs.info,
|
||||
() => Get.to(() => const AddStaffPage(role: 'service')),
|
||||
),
|
||||
],
|
||||
@@ -1297,25 +1468,25 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
'title': 'مكتملة',
|
||||
'value': data['completed_rides'],
|
||||
'icon': Icons.check_circle_rounded,
|
||||
'color': const Color(0xFF10B981),
|
||||
'color': cs.success,
|
||||
},
|
||||
{
|
||||
'title': 'ملغاة',
|
||||
'value': data['cancelled_rides'],
|
||||
'icon': Icons.cancel_rounded,
|
||||
'color': const Color(0xFFEF4444),
|
||||
'color': cs.danger,
|
||||
},
|
||||
{
|
||||
'title': 'مدفوعات',
|
||||
'value': _formatCurrency(data['payments']),
|
||||
'icon': Icons.attach_money_rounded,
|
||||
'color': const Color(0xFFF59E0B),
|
||||
'color': cs.warning,
|
||||
},
|
||||
{
|
||||
'title': 'Comfort',
|
||||
'value': data['comfort'],
|
||||
'icon': Icons.chair_rounded,
|
||||
'color': const Color(0xFF10B981),
|
||||
'color': cs.success,
|
||||
},
|
||||
{
|
||||
'title': 'Speed',
|
||||
|
||||
Reference in New Issue
Block a user