Files
intaleq/intaleq_admin/lib/views/admin/admin_home_page.dart
T
Hamza-AyedandClaude Opus 5 0aab85c732 refactor: إعادة تسمية التطبيقات الأربعة siro_* → intaleq_*
المجلدات وأسماء حزم dart واستيراداتها:
  siro_rider → intaleq_rider    siro_driver  → intaleq_driver
  siro_admin → intaleq_admin    siro_service → intaleq_service

شمل ذلك `name:` في كل pubspec وكل `package:siro_*` في الاستيرادات
(115 ملفاً في rider وحده)، وإشارات أسماء المجلدات في docs/ وتعليق في
backend/Admin/notifications/broadcast.php. لم تبقَ إشارة واحدة (تحقّقت).

+ ضُمّت حزم get و get_storage داخل intaleq_driver/packages/ وحُوّلت
مساراتها الثلاثة من `../../Intaleq/packages/*` إلى `./packages/*`. كانت
تُحل عرضاً إلى مجلد App/Intaleq القديم المجاور — تبعية خارج المستودع
تنكسر بأول نقل. لم يبقَ في أي تطبيق تبعية مسار خارج الشجرة.

⚠️ لم تُمسّ بعد: هويات المتاجر (applicationId · PRODUCT_BUNDLE_IDENTIFIER ·
shorebird app_id) ولا الألوان ولا النصوص المرئية ولا النطاقات — كل منها
كوميت منفصل، والهويات تحتاج قرار المالك.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 16:10:58 +03:00

1520 lines
56 KiB
Dart

import 'dart:math';
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:intaleq_admin/views/admin/drivers/driver_gift_check_page.dart';
import 'package:intaleq_admin/views/admin/drivers/driver_tracker_screen.dart';
import 'package:intaleq_admin/views/admin/quality/blacklist_page.dart';
import '../../constant/box_name.dart';
import '../../controller/admin/dashboard_controller.dart';
import '../../controller/admin/static_controller.dart';
import '../../controller/functions/crud.dart';
import '../../controller/notification_controller.dart';
import '../../main.dart';
import '../../print.dart';
import '../widgets/snackbar.dart';
import '../invoice/invoice_list_page.dart';
import 'captain/captain.dart';
import 'captain/syrian_driver_not_active.dart';
import 'drivers/monitor_ride.dart';
import 'employee/employee_page.dart';
import 'enceypt/encrypt.dart';
import 'error/error/error_page.dart';
import 'packages.dart';
import 'passenger/passenger.dart';
import 'rides/ride_lookup_page.dart';
import 'server/monitor_server_page.dart';
import 'static/static.dart';
import 'wallet/wallet.dart';
import 'staff/add_staff_page.dart';
import 'staff/pending_admins_page.dart';
import 'dashboard_v2_widget.dart';
import 'static/advanced_analytics_page.dart';
import 'financial/financial_v2_page.dart';
import 'security/audit_logs_page.dart';
import 'analytics/live_analytics_page.dart';
import 'package:intaleq_admin/views/widgets/responsive_layout.dart';
import 'package:intaleq_admin/views/widgets/web_sidebar.dart';
import '../transit/org_list_page.dart';
import 'package:intaleq_admin/views/widgets/glass_container.dart';
class AdminHomePage extends StatefulWidget {
const AdminHomePage({super.key});
@override
State<AdminHomePage> createState() => _AdminHomePageState();
}
class _AdminHomePageState extends State<AdminHomePage>
with SingleTickerProviderStateMixin {
final TextEditingController _messageController = TextEditingController();
final TextEditingController _searchController = TextEditingController();
late AnimationController _pulseController;
late bool isSuperAdmin;
late DashboardController dashboardController;
String _searchQuery = '';
@override
void initState() {
super.initState();
_pulseController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
final String role = box.read('admin_role')?.toString() ?? 'admin';
final String myPhone = box.read(BoxName.adminPhone)?.toString() ?? '';
isSuperAdmin = (role == 'super_admin') ||
(myPhone == '201023248456' ||
myPhone == '963992952235' ||
myPhone == '963942542053');
Log.print('AdminHomePage: role=$role, isSuperAdmin=$isSuperAdmin');
dashboardController = Get.put(DashboardController());
}
@override
void dispose() {
_pulseController.dispose();
_messageController.dispose();
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return ResponsiveLayout(
mobile: _buildMobileScaffold(cs),
desktop: Scaffold(
backgroundColor: cs.surface,
body: Row(
children: [
const WebSidebar(selectedIndex: 0),
Expanded(child: _buildDesktopScaffold(cs)),
],
),
),
);
}
Widget _buildMobileScaffold(ColorScheme cs) {
return Scaffold(
backgroundColor: cs.surface,
body: RefreshIndicator(
onRefresh: () async => await dashboardController.getDashBoard(),
color: cs.primary,
backgroundColor: cs.surfaceContainerHighest,
child: GetBuilder<DashboardController>(
builder: (controller) {
if (controller.dashbord.isEmpty) {
return _buildLoadingState(cs);
}
final data = controller.dashbord[0];
final categories = _getFilteredCategories();
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(controller, cs),
_buildSearchBar(cs),
if (_searchQuery.isEmpty) const DashboardV2Widget(),
if (_searchQuery.isEmpty)
_buildQuickStatsSection(data, controller, cs),
SliverPadding(
padding: const EdgeInsets.only(bottom: 60),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final category = categories[index];
if (category.items.isEmpty) {
return const SizedBox.shrink();
}
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 450),
child: SlideAnimation(
verticalOffset: 40.0,
child: FadeInAnimation(
child: _buildCategorySection(category, cs),
),
),
);
},
childCount: categories.length,
),
),
),
],
);
},
),
),
);
}
Widget _buildDesktopScaffold(ColorScheme cs) {
return Scaffold(
backgroundColor: cs.surface,
body: GetBuilder<DashboardController>(
builder: (controller) {
if (controller.dashbord.isEmpty) {
return _buildLoadingState(cs);
}
final data = controller.dashbord[0];
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(controller, cs),
const DashboardV2Widget(),
_buildQuickStatsSection(data, controller, cs),
],
);
},
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [cs.primary.withValues(alpha: 0.3), Colors.transparent],
),
),
child: Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2.5,
),
),
),
),
const SizedBox(height: 16),
Text('جاري التحميل...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildSliverAppBar(DashboardController controller, ColorScheme cs) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return SliverAppBar(
expandedHeight: 130.0,
floating: true,
pinned: true,
backgroundColor: cs.surface,
elevation: 0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [
cs.primary.withValues(alpha: 0.08),
cs.surface,
cs.tertiary.withValues(alpha: 0.05),
]
: [
cs.primary.withValues(alpha: 0.05),
cs.surface,
cs.tertiary.withValues(alpha: 0.03),
],
),
),
),
Positioned(
top: -30,
left: -40,
child: _GlowOrb(color: cs.primary, size: 150, opacity: 0.08),
),
Positioned(
top: -20,
right: -20,
child: _GlowOrb(color: cs.tertiary, size: 120, opacity: 0.06),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 18),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildLogo(cs),
const SizedBox(height: 6),
Text(
isSuperAdmin ? 'Super Admin Panel' : 'Admin Panel',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
letterSpacing: 1.5,
),
),
],
),
),
),
],
),
),
actions: [
_buildHeaderAction(
Icons.refresh_rounded, () => controller.getDashBoard(), cs),
const SizedBox(width: 8),
],
);
}
Widget _buildLogo(ColorScheme cs) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedBuilder(
animation: _pulseController,
builder: (_, __) {
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
cs.primary.withValues(
alpha: 0.3 + 0.1 * _pulseController.value),
cs.primary.withValues(alpha: 0.1),
],
),
border: Border.all(
color: cs.primary.withValues(
alpha: 0.4 + 0.2 * _pulseController.value),
width: 1,
),
),
child: Icon(
Icons.admin_panel_settings_rounded,
color: cs.primary,
size: 18,
),
);
},
),
const SizedBox(width: 10),
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [cs.primary, cs.tertiary],
).createShader(bounds),
child: const Text(
'Siro Admin',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 20,
letterSpacing: 0.5,
),
),
),
],
);
}
Widget _buildHeaderAction(IconData icon, VoidCallback onTap, ColorScheme cs) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(icon, color: cs.onSurfaceVariant, size: 18),
),
);
}
Widget _buildSearchBar(ColorScheme cs) {
final isFocused = _searchQuery.isNotEmpty;
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isFocused ? cs.primary.withValues(alpha: 0.5) : cs.outline,
width: isFocused ? 1.5 : 1,
),
),
child: TextField(
controller: _searchController,
onChanged: (val) => setState(() => _searchQuery = val),
style: TextStyle(color: cs.onSurface, fontSize: 14),
decoration: InputDecoration(
hintText: 'ابحث عن خدمة أو ميزة...',
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon:
Icon(Icons.search_rounded, color: cs.primary, size: 20),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: Icon(Icons.close_rounded,
color: cs.onSurfaceVariant, size: 18),
onPressed: () {
setState(() {
_searchQuery = '';
_searchController.clear();
});
},
)
: null,
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
),
),
),
);
}
Widget _buildQuickStatsSection(
dynamic data, DashboardController controller, ColorScheme cs) {
final highlights = [
_HighlightData(
'إجمالي الركاب', data['countPassengers'], Icons.group_rounded, cs.tertiary),
_HighlightData('إجمالي السائقين', data['countDriver'],
Icons.drive_eta_rounded, cs.secondary),
_HighlightData('رحلات الشهر', data['countRideThisMonth'],
Icons.calendar_today_rounded, cs.primary),
if (isSuperAdmin)
_HighlightData('المحفظة', _formatCurrency(data['seferWallet']),
Icons.account_balance_wallet_rounded, cs.primary),
];
final detailedStats = _getDetailedStats(data, controller, cs);
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text('نظرة عامة',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
)),
],
),
),
SizedBox(
height: 108,
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
itemCount: highlights.length,
itemBuilder: (ctx, i) => Padding(
padding: EdgeInsets.only(
right: i < highlights.length - 1 ? 10 : 0),
child: _buildHighlightCard(highlights[i], cs),
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(detailedStats.length, (i) {
final stat = detailedStats[i];
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailStatItem(stat, cs),
if (i < detailedStats.length - 1)
Container(
width: 1,
height: 36,
color: cs.outline,
),
],
);
}),
),
),
),
),
),
_buildChartsSection(data, controller, cs),
const SizedBox(height: 8),
],
),
),
);
}
Widget _buildChartsSection(
dynamic data, DashboardController controller, ColorScheme cs) {
final completed = (data['completed_rides'] ?? 0).toDouble();
final cancelled = (data['cancelled_rides'] ?? 0).toDouble();
final total = completed + cancelled;
final completedPercent = total > 0 ? (completed / total * 100).toStringAsFixed(1) : '0.0';
final comfort = (data['comfort'] ?? 0).toDouble();
final speed = (data['speed'] ?? 0).toDouble();
final lady = (data['lady'] ?? 0).toDouble();
final maxBar = [comfort, speed, lady].reduce((a, b) => a > b ? a : b);
final barMaxY = maxBar > 0 ? (maxBar * 1.3).ceilToDouble() : 10.0;
final payments = (data['payments'] ?? 0).toDouble();
final passengerCount = (data['countPassengers'] ?? 0).toDouble();
final driverCount = (data['countDriver'] ?? 0).toDouble();
final monthRides = (data['countRideThisMonth'] ?? 0).toDouble();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 10),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text('تحليل البيانات',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
)),
],
),
),
Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 200,
child: Stack(
alignment: Alignment.center,
children: [
PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: 35,
sections: [
PieChartSectionData(
value: completed,
color: cs.primary,
radius: 55,
title: '',
),
PieChartSectionData(
value: cancelled,
color: cs.error,
radius: 55,
title: '',
),
],
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$completedPercent%',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
total.toInt().toString(),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
],
),
],
),
),
),
const SizedBox(width: 8),
Expanded(
child: SizedBox(
height: 200,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: barMaxY,
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
getTooltipColor: (_) => cs.surfaceContainerHighest,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
rod.toY.toInt().toString(),
TextStyle(
color: cs.onSurface,
fontSize: 11,
fontWeight: FontWeight.w600,
),
);
},
),
),
titlesData: FlTitlesData(
show: true,
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
final labels = ['Comfort', 'Speed', 'Lady'];
if (value.toInt() < labels.length) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
labels[value.toInt()],
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
);
}
return const SizedBox.shrink();
},
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(
x: 0,
barRods: [
BarChartRodData(
toY: comfort,
color: cs.tertiary,
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
BarChartGroupData(
x: 1,
barRods: [
BarChartRodData(
toY: speed,
color: const Color(0xFFF59E0B),
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
BarChartGroupData(
x: 2,
barRods: [
BarChartRodData(
toY: lady,
color: const Color(0xFFF48FB1),
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
],
),
),
),
),
],
),
),
const SizedBox(height: 12),
AnimationConfiguration.staggeredList(
position: 0,
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.account_balance_rounded, color: cs.primary, size: 18),
const SizedBox(width: 8),
Text('نظرة عامة على الإيرادات', style: TextStyle(color: cs.onSurface, fontSize: 13, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 16),
SizedBox(
height: 120,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: (payments * 1.3) > 0 ? (payments * 1.3) : 10,
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
getTooltipColor: (_) => cs.surfaceContainerHighest,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
_formatCurrency(rod.toY),
TextStyle(color: cs.onSurface, fontSize: 11, fontWeight: FontWeight.w600),
);
},
),
),
titlesData: FlTitlesData(
show: true,
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Text('Payments', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)),
);
},
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(
x: 0,
barRods: [
BarChartRodData(
toY: payments,
color: cs.primary,
width: 40,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
],
),
),
),
const SizedBox(height: 12),
Center(
child: Text(
'إجمالي المدفوعات: ${_formatCurrency(payments)}',
style: TextStyle(color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.bold),
),
),
],
),
),
),
),
),
const SizedBox(height: 12),
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.compare_arrows_rounded, color: cs.primary, size: 18),
const SizedBox(width: 8),
Text('نسبة السائقين إلى الركاب', style: TextStyle(color: cs.onSurface, fontSize: 13, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 16),
_buildRatioBar('السائقين', driverCount, driverCount + passengerCount, cs.secondary, cs),
const SizedBox(height: 12),
_buildRatioBar('الركاب', passengerCount, driverCount + passengerCount, cs.tertiary, cs),
],
),
),
),
),
),
const SizedBox(height: 12),
AnimationConfiguration.staggeredList(
position: 2,
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: Row(
children: [
_buildMiniStatCard('رحلات الشهر', monthRides, Icons.calendar_today_rounded, cs.primary, cs),
_buildMiniStatCard('الركاب', passengerCount, Icons.group_rounded, cs.tertiary, cs),
_buildMiniStatCard('السائقين', driverCount, Icons.drive_eta_rounded, cs.secondary, cs),
],
),
),
),
),
),
],
),
);
}
Widget _buildRatioBar(String label, double value, double total, Color color, ColorScheme cs) {
final ratio = total > 0 ? value / total : 0.0;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11)),
Text(value.toInt().toString(), style: TextStyle(color: cs.onSurface, fontSize: 12, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: ratio,
backgroundColor: cs.surface,
color: color,
minHeight: 12,
),
),
],
);
}
Widget _buildMiniStatCard(String label, dynamic value, IconData icon, Color color, ColorScheme cs) {
return Expanded(
child: Column(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(height: 8),
Text(value.toString(), style: TextStyle(color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 2),
Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)),
],
),
);
}
Widget _buildHighlightCard(_HighlightData h, ColorScheme cs) {
return GlassContainer(
width: 155,
padding: const EdgeInsets.all(16),
borderRadius: 16,
gradientColors: [
h.color.withValues(alpha: 0.18),
h.color.withValues(alpha: 0.03),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: h.color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(9),
),
child: Icon(h.icon, color: h.color, size: 16),
),
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: h.color.withValues(alpha: 0.6),
),
),
],
),
const Spacer(),
Text(
h.value.toString(),
style: TextStyle(
color: cs.onSurface,
fontSize: 20,
fontWeight: FontWeight.w700,
height: 1.1,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 3),
Text(
h.label,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
],
),
);
}
Widget _buildDetailStatItem(Map<String, dynamic> stat, ColorScheme cs) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(stat['icon'] as IconData,
color: stat['color'] as Color, size: 20),
const SizedBox(height: 6),
Text(
stat['value'].toString(),
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.bold,
height: 1,
),
),
const SizedBox(height: 3),
Text(
stat['title'] as String,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 9,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildCategorySection(ActionCategory category, ColorScheme cs) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 12),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
category.title,
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
letterSpacing: 0.3,
),
),
const SizedBox(width: 10),
Expanded(
child: Container(height: 1, color: cs.outline),
),
const SizedBox(width: 8),
Text(
'${category.items.length}',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w500,
),
),
],
),
),
GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120,
childAspectRatio: 0.88,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: category.items.length,
itemBuilder: (context, index) =>
_buildActionItem(category.items[index], cs),
),
const SizedBox(height: 8),
],
);
}
Widget _buildActionItem(ActionItem item, ColorScheme cs) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: item.onPressed,
borderRadius: BorderRadius.circular(16),
splashColor: item.color.withValues(alpha: 0.1),
highlightColor: item.color.withValues(alpha: 0.05),
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(11),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
item.color.withValues(alpha: 0.20),
item.color.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: item.color.withValues(alpha: 0.25)),
boxShadow: [
BoxShadow(
color: item.color.withValues(alpha: 0.15),
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
child: Icon(item.icon, color: item.color, size: 22),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
item.title,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: cs.onSurface,
fontSize: 11,
fontWeight: FontWeight.w500,
height: 1.3,
),
),
),
],
),
),
),
);
}
List<ActionCategory> _getFilteredCategories() {
final all = _getAllActionCategories();
if (_searchQuery.isEmpty) return all;
return all
.map((cat) {
final matched = cat.items
.where((item) =>
item.title.toLowerCase().contains(_searchQuery.toLowerCase()))
.toList();
return matched.isEmpty
? null
: ActionCategory(title: cat.title, items: matched);
})
.whereType<ActionCategory>()
.toList();
}
List<ActionCategory> _getAllActionCategories() {
return [
ActionCategory(
title: 'المستخدمين',
items: [
ActionItem('الركاب', Icons.people_outline_rounded, const Color(0xFF3B82F6),
() => Get.to(() => Passengrs())),
ActionItem('السائقون', Icons.drive_eta_rounded, const Color(0xFFF59E0B),
() => Get.to(() => CaptainsPage())),
ActionItem('المراقب', Icons.track_changes_rounded, const Color(0xFFEF4444),
() => Get.to(() => SiroTrackerScreen())),
],
),
ActionCategory(
title: 'مواصلاتي',
items: [
ActionItem('المؤسسات', Icons.directions_bus_filled_rounded, const Color(0xFF6366F1),
() => Get.to(() => const TransitOrgListPage())),
],
),
ActionCategory(
title: 'إدارة النظام الجديد',
items: [
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1),
() => Get.toNamed('/promo')),
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, const Color(0xFFF59E0B),
() => Get.toNamed('/kazan')),
ActionItem('الشكاوى', Icons.report_problem_rounded, const Color(0xFFEF4444),
() => Get.toNamed('/complaints')),
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, const Color(0xFF3B82F6),
() => Get.toNamed('/driver-docs')),
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
() => Get.toNamed('/social-intelligence')),
],
),
ActionCategory(
title: 'العمليات',
items: [
ActionItem('الرحلات', Icons.map_rounded, const Color(0xFF82AAFF),
() => Get.to(() => RidesDashboardScreen())),
if (isSuperAdmin)
ActionItem(
'مراقبة الرحلات',
Icons.remove_red_eye_rounded,
const Color(0xFFC792EA),
() => Get.to(() => RideMonitorScreen())),
ActionItem('الإحصائيات', Icons.bar_chart_rounded, const Color(0xFF6366F1), () async {
await Get.put(StaticController()).getAll();
Get.to(() => const StaticDash());
}),
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, const Color(0xFF3B82F6),
() => Get.to(() => const AdvancedAnalyticsPage())),
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
const Color(0xFF00CEC9),
() => Get.to(() => const LiveAnalyticsPage())),
],
),
ActionCategory(
title: 'الجودة والدعم',
items: [
ActionItem('القائمة السوداء', Icons.block_flipped, const Color(0xFFEF4444),
() => Get.to(() => const BlacklistPage())),
],
),
if (true)
ActionCategory(
title: 'المالية والإدارة',
items: [
ActionItem('الإدارة المالية V2', Icons.account_balance_rounded,
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),
() => Get.to(() => DriverGiftCheckPage())),
ActionItem('الفواتير', Icons.receipt_long_rounded,
const Color(0xFF10B981), () => Get.to(() => InvoiceListPage())),
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF),
() => Get.to(() => EmployeePage())),
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1),
() => Get.to(() => const PendingAdminsPage())),
],
),
if (true)
ActionCategory(
title: 'النظام والتواصل',
items: [
ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded,
const Color(0xFFEF4444), () => Get.to(() => const AuditLogsPage())),
ActionItem('واتساب جماعي', Icons.message_rounded,
const Color(0xFF25D366), () => _showWhatsAppDialog(context)),
ActionItem('أتمتة التسويق', Icons.campaign_rounded,
const Color(0xFF10B981), () => Get.toNamed('/marketing')),
ActionItem(
'إشعار سائقين',
Icons.notifications_active_rounded,
const Color(0xFFFF7043),
() => Get.put(NotificationController())
.sendNotificationDrivers()),
ActionItem(
'إشعار ركاب',
Icons.notification_important_rounded,
const Color(0xFFF06292),
() => Get.put(NotificationController())
.sendNotificationPassengers()),
ActionItem('تسجيل سائق', Icons.person_add_rounded, const Color(0xFF3B82F6),
() => Get.to(() => DriversPendingPage())),
ActionItem(
'تحديث التطبيق',
Icons.system_update_rounded,
const Color(0xFF9CA3AF),
() => Get.to(() => PackageUpdateScreen())),
ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1),
() => Get.to(() => ServerMonitorPage())),
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, const Color(0xFFEF4444),
() => Get.to(() => ErrorListPage())),
ActionItem(
'أداة التشفير',
Icons.lock_rounded,
const Color(0xFF9575CD),
() => Get.to(() => EncryptToolPage(
adminToken: box.read(BoxName.adminPhone),
))),
],
),
if (isSuperAdmin)
ActionCategory(
title: 'إدارة الكوادر',
items: [
ActionItem(
'إضافة مدير',
Icons.admin_panel_settings_rounded,
const Color(0xFF6366F1),
() => Get.to(() => const AddStaffPage(role: 'admin')),
),
ActionItem(
'إضافة خدمة عملاء',
Icons.support_agent_rounded,
const Color(0xFF3B82F6),
() => Get.to(() => const AddStaffPage(role: 'service')),
),
],
),
];
}
List<Map<String, dynamic>> _getDetailedStats(
dynamic data, DashboardController controller, ColorScheme cs) {
return [
if (isSuperAdmin)
{
'title': 'رصيد الرسائل',
'value': controller.creditSMS,
'icon': Icons.sms_rounded,
'color': cs.tertiary,
},
{
'title': 'مكتملة',
'value': data['completed_rides'],
'icon': Icons.check_circle_rounded,
'color': const Color(0xFF10B981),
},
{
'title': 'ملغاة',
'value': data['cancelled_rides'],
'icon': Icons.cancel_rounded,
'color': const Color(0xFFEF4444),
},
{
'title': 'مدفوعات',
'value': _formatCurrency(data['payments']),
'icon': Icons.attach_money_rounded,
'color': const Color(0xFFF59E0B),
},
{
'title': 'Comfort',
'value': data['comfort'],
'icon': Icons.chair_rounded,
'color': const Color(0xFF10B981),
},
{
'title': 'Speed',
'value': data['speed'],
'icon': Icons.flash_on_rounded,
'color': const Color(0xFFFBBF24),
},
{
'title': 'Lady',
'value': data['lady'],
'icon': Icons.woman_rounded,
'color': const Color(0xFFF48FB1),
},
];
}
void _showWhatsAppDialog(BuildContext context) {
final cs = Theme.of(context).colorScheme;
Get.dialog(
Dialog(
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.3),
blurRadius: 30,
offset: const Offset(0, 12),
),
],
),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF25D366).withValues(alpha: 0.12),
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF25D366).withValues(alpha: 0.25)),
),
child: const Icon(Icons.message_rounded,
color: Color(0xFF25D366), size: 28),
),
const SizedBox(height: 16),
Text(
'إرسال واتساب جماعي',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
'سيتم إرسال الرسالة لجميع السائقين',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11),
),
const SizedBox(height: 20),
Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.outline),
),
child: TextField(
controller: _messageController,
maxLines: 4,
style: TextStyle(color: cs.onSurface, fontSize: 13),
decoration: InputDecoration(
hintText: 'اكتب رسالتك هنا...',
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(14),
),
),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: cs.outline),
),
),
child: Text(
'إلغاء',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.send_rounded, size: 16),
label:
const Text('إرسال', style: TextStyle(fontSize: 13)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF25D366),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
onPressed: () async {
if (_messageController.text.isNotEmpty) {
Get.back();
var driverPhones = box
.read(BoxName.tokensDrivers)['message'] as List?;
if (driverPhones == null || driverPhones.isEmpty) {
return;
}
mySnackbarInfo('سيتم الإرسال في الخلفية');
for (var driverData in driverPhones) {
if (driverData['phone'] != null) {
await CRUD().sendWhatsAppAuth(
driverData['phone'].toString(),
_messageController.text);
await Future.delayed(
Duration(seconds: Random().nextInt(3) + 1));
}
}
_messageController.clear();
}
},
),
),
],
),
],
),
),
),
);
}
String _formatCurrency(dynamic value) {
if (value == null) return '0.0';
return double.tryParse(value.toString())?.toStringAsFixed(1) ?? '0.0';
}
}
class _GlowOrb extends StatelessWidget {
final Color color;
final double size;
final double opacity;
const _GlowOrb(
{required this.color, required this.size, required this.opacity});
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [color.withValues(alpha: opacity), Colors.transparent],
),
),
);
}
}
class _HighlightData {
final String label;
final dynamic value;
final IconData icon;
final Color color;
_HighlightData(this.label, this.value, this.icon, this.color);
}
class ActionItem {
final String title;
final IconData icon;
final Color color;
final VoidCallback onPressed;
ActionItem(this.title, this.icon, this.color, this.onPressed);
}
class ActionCategory {
final String title;
final List<ActionItem> items;
ActionCategory({required this.title, required this.items});
}