This commit is contained in:
Hamza-Ayed
2026-05-01 01:43:59 +03:00
parent cdda136006
commit 5fc160e374
25 changed files with 1526 additions and 1856 deletions

View File

@@ -13,6 +13,7 @@ import '../../controller/admin/static_controller.dart';
import '../../controller/functions/crud.dart';
import '../../controller/notification_controller.dart';
import '../../main.dart';
import '../../print.dart';
import '../invoice/invoice_list_page.dart';
import 'captain/captain.dart';
import 'captain/syrian_driver_not_active.dart';
@@ -28,6 +29,8 @@ 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';
class AdminHomePage extends StatefulWidget {
const AdminHomePage({super.key});
@@ -69,10 +72,16 @@ class _AdminHomePageState extends State<AdminHomePage>
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
String myPhone = box.read(BoxName.adminPhone).toString();
isSuperAdmin = myPhone == '201023248456' ||
myPhone == '963992952235' ||
myPhone == '963942542053';
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());
}
@@ -760,6 +769,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const Color(0xFF80CBC4), () => Get.to(() => InvoiceListPage())),
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFFB0BEC5),
() => Get.to(() => EmployeePage())),
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, _accent,
() => Get.to(() => const PendingAdminsPage())),
],
),
if (isSuperAdmin)
@@ -804,19 +815,37 @@ class _AdminHomePageState extends State<AdminHomePage>
))),
],
),
if (isSuperAdmin)
ActionCategory(
title: 'إدارة الكوادر',
items: [
ActionItem(
'إضافة مدير',
Icons.admin_panel_settings_rounded,
_accent,
() => Get.to(() => const AddStaffPage(role: 'admin')),
),
ActionItem(
'إضافة خدمة عملاء',
Icons.support_agent_rounded,
_info,
() => Get.to(() => const AddStaffPage(role: 'service')),
),
],
),
];
}
List<Map<String, dynamic>> _getDetailedStats(
dynamic data, DashboardController controller) {
return [
// if (isSuperAdmin)
// {
// 'title': 'رصيد الرسائل',
// 'value': controller.creditSMS,
// 'icon': Icons.sms_rounded,
// 'color': _info,
// },
if (isSuperAdmin)
{
'title': 'رصيد الرسائل',
'value': controller.creditSMS,
'icon': Icons.sms_rounded,
'color': _info,
},
{
'title': 'مكتملة',
'value': data['completed_rides'],

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_admin1/controller/functions/crud.dart';
import 'package:sefer_admin1/controller/functions/wallet.dart'; // تأكد من المسار
import 'package:sefer_admin1/controller/functions/wallet.dart';
import '../../../constant/links.dart'; // تأكد من المسار
// --- Controller: المسؤول عن المنطق (البحث، الفحص، الإضافة) ---
class DriverGiftCheckerController extends GetxController {
@@ -25,8 +27,7 @@ class DriverGiftCheckerController extends GetxController {
Future<void> fetchDriverCache() async {
try {
final response = await CRUD().post(
link:
'https://api.intaleq.xyz/intaleq/Admin/driver/getDriverGiftPayment.php',
link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php',
payload: {'phone': phoneController.text.trim()},
);
// print('response: ${response}');
@@ -55,7 +56,17 @@ class DriverGiftCheckerController extends GetxController {
try {
// الخطوة 1: استخراج الـ ID بناءً على رقم الهاتف
var driver = driversCache.firstWhere(
(d) => d['phone'].toString().contains(phoneInput),
(d) {
String dbPhone =
d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), '');
String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), '');
// قارن آخر 9 أرقام لتجاوز مشكلة 09 مقابل 963
if (dbPhone.length >= 9 && inputPhone.length >= 9) {
return dbPhone.substring(dbPhone.length - 9) ==
inputPhone.substring(inputPhone.length - 9);
}
return dbPhone == inputPhone;
},
orElse: () => null,
);

View File

@@ -9,6 +9,7 @@ import 'package:sefer_admin1/constant/links.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/box_name.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
class IntaleqTrackerScreen extends StatefulWidget {
@@ -107,12 +108,17 @@ class _IntaleqTrackerScreenState extends State<IntaleqTrackerScreen>
try {
String updateUrl =
"${_baseDir}getUpdatedLocationForAdmin.php?mode=${isLiveMode ? 'live' : 'day'}";
await http.get(Uri.parse(updateUrl));
print("📡 Calling Update URL: $updateUrl");
var responseUpdate = await CRUD().post(link: updateUrl, payload: {});
print("📡 Update Response: $responseUpdate");
String v = DateTime.now().millisecondsSinceEpoch.toString();
final responseLive =
await http.get(Uri.parse("${_baseDir}locations_live.json?v=$v"));
String liveUrl = "${_baseDir}locations_live.json?v=$v";
print("📡 Calling Live JSON URL: $liveUrl");
final responseLive = await http.get(Uri.parse(liveUrl));
print(
"📡 Live JSON Response (${responseLive.statusCode}): ${responseLive.body.length > 100 ? responseLive.body.substring(0, 100) : responseLive.body}");
if (responseLive.statusCode == 200) {
final data = json.decode(responseLive.body);
List drivers = (data is Map && data.containsKey('drivers'))
@@ -125,8 +131,11 @@ class _IntaleqTrackerScreenState extends State<IntaleqTrackerScreen>
});
}
final responseDay =
await http.get(Uri.parse("${_baseDir}locations_day.json?v=$v"));
String dayUrl = "${_baseDir}locations_day.json?v=$v";
print("📡 Calling Day JSON URL: $dayUrl");
final responseDay = await http.get(Uri.parse(dayUrl));
print(
"📡 Day JSON Response (${responseDay.statusCode}): ${responseDay.body.length > 100 ? responseDay.body.substring(0, 100) : responseDay.body}");
if (responseDay.statusCode == 200) {
final data = json.decode(responseDay.body);
List drivers = (data is Map && data.containsKey('drivers'))

View File

@@ -20,7 +20,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:sefer_admin1/controller/functions/encrypt_decrypt.dart' as X;
import '../../../constant/char_map.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
@@ -240,8 +242,7 @@ class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
Container(
child: TextButton(
onPressed: () {
print(EncryptionHelper.instance.decryptData(
'dab40749cdecbfddf4696566448b384f0d272705b08b4ff779e085fbf3257026'));
print(EncryptionHelper.instance.decryptData('hbgbitbXrXrBr'));
},
child: Text(
"Decrypt Test",
@@ -259,6 +260,16 @@ class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
),
),
),
Container(
child: TextButton(
onPressed: () {
print(r('hbgbitbXrXrBr'));
},
child: Text(
"decrypt X.r",
),
),
),
const SizedBox(height: 24),

View File

@@ -537,11 +537,19 @@ class PackageController extends GetxController {
fetchPackages() async {
isLoading.value = true;
var response = await CRUD().get(link: AppLink.getPackages, payload: {});
if (response != 'failure') {
var jsonData = jsonDecode(response);
packages = jsonData['message'];
if (response is String && (response == 'failure' || response == 'token_expired')) {
isLoading.value = false;
return;
}
try {
var jsonData = response is String ? jsonDecode(response) : response;
packages = jsonData['message'] ?? [];
Log.print('✅ Decoded packages: ${packages.length} items');
update();
Log.print('jsonData: $jsonData');
} catch (e) {
Log.print('❌ Error parsing packages: $e');
}
isLoading.value = false;
}

View File

@@ -0,0 +1,195 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/admin/staff_controller.dart';
class AddStaffPage extends StatelessWidget {
final String role; // 'admin' or 'service'
const AddStaffPage({super.key, required this.role});
@override
Widget build(BuildContext context) {
final controller = Get.put(StaffController());
controller.selectedRole = role;
const Color bgColor = Color(0xFF0D1117);
const Color inputColor = Color(0xFF161B22);
const Color accentColor = Color(0xFF00D4AA);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: Text(role == 'admin' ? "إضافة مدير جديد" : "إضافة موظف خدمة عملاء"),
backgroundColor: bgColor,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle("المعلومات الأساسية"),
const SizedBox(height: 16),
_buildTextField(
controller: controller.nameController,
label: "الاسم الكامل",
icon: Icons.person_outline,
fillColor: inputColor,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.phoneController,
label: "رقم الهاتف",
icon: Icons.phone_android_outlined,
fillColor: inputColor,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.emailController,
label: "البريد الإلكتروني",
icon: Icons.email_outlined,
fillColor: inputColor,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.passwordController,
label: "كلمة المرور",
icon: Icons.lock_outline,
fillColor: inputColor,
obscureText: true,
),
const SizedBox(height: 24),
_buildSectionTitle("معلومات إضافية"),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildDropdown(
label: "الجنس",
value: controller.selectedGender,
items: ['Male', 'Female'],
onChanged: (val) => controller.selectedGender = val!,
fillColor: inputColor,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: controller.birthdateController,
label: "تاريخ الميلاد",
icon: Icons.calendar_today_outlined,
fillColor: inputColor,
hint: "YYYY-MM-DD",
),
),
],
),
const SizedBox(height: 40),
GetBuilder<StaffController>(
builder: (controller) => SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.isLoading ? null : () => controller.registerStaff(),
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: controller.isLoading
? const CircularProgressIndicator(color: Colors.white)
: Text(
"حفظ البيانات",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: bgColor,
),
),
),
),
),
],
),
),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: const TextStyle(
color: Color(0xFF7D8590),
fontSize: 13,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String label,
required IconData icon,
required Color fillColor,
String? hint,
bool obscureText = false,
TextInputType keyboardType = TextInputType.text,
}) {
return Container(
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: TextFormField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: label,
hintText: hint,
hintStyle: const TextStyle(color: Colors.white24),
labelStyle: const TextStyle(color: Colors.white54),
prefixIcon: Icon(icon, color: Colors.white38),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
validator: (val) => val == null || val.isEmpty ? "هذا الحقل مطلوب" : null,
),
);
}
Widget _buildDropdown({
required String label,
required String value,
required List<String> items,
required Function(String?) onChanged,
required Color fillColor,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: DropdownButtonFormField<String>(
value: value,
dropdownColor: fillColor,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.white54),
border: InputBorder.none,
),
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
onChanged: onChanged,
),
);
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../widgets/snackbar.dart';
class PendingAdminsPage extends StatefulWidget {
const PendingAdminsPage({super.key});
@override
State<PendingAdminsPage> createState() => _PendingAdminsPageState();
}
class _PendingAdminsPageState extends State<PendingAdminsPage> {
final CRUD _crud = CRUD();
bool _isLoading = true;
List _pendingAdmins = [];
@override
void initState() {
super.initState();
_fetchPendingAdmins();
}
Future<void> _fetchPendingAdmins() async {
setState(() => _isLoading = true);
try {
final response = await _crud.post(
link: '${AppLink.server}/Admin/auth/list_pending.php',
);
if (response != 'failure') {
setState(() {
_pendingAdmins = response['message'] ?? [];
});
}
} catch (e) {
mySnackeBarError('فشل في جلب البيانات: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _handleAction(String adminId, String action) async {
try {
final response = await _crud.post(
link: '${AppLink.server}/Admin/auth/approve_admin.php',
payload: {
'admin_id': adminId,
'action': action,
},
);
if (response != 'failure') {
mySnackbarSuccess('تم تنفيذ الإجراء بنجاح');
_fetchPendingAdmins(); // تحديث القائمة
}
} catch (e) {
mySnackeBarError('حدث خطأ: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0A0D14),
appBar: AppBar(
title: const Text('طلبات الانضمام المعلقة', style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: const Color(0xFF161D2E),
elevation: 0,
),
body: _isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFF00E5FF)))
: _pendingAdmins.isEmpty
? _buildEmptyState()
: _buildList(),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.person_search_rounded, size: 80, color: Colors.grey[800]),
const SizedBox(height: 16),
const Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: Colors.grey)),
],
),
);
}
Widget _buildList() {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _pendingAdmins.length,
itemBuilder: (context, index) {
final admin = _pendingAdmins[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF161D2E),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF1F2D4A)),
),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: const CircleAvatar(
backgroundColor: Color(0xFF1F2D4A),
child: Icon(Icons.person, color: Color(0xFF00E5FF)),
),
title: Text(admin['name'] ?? 'بدون اسم', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
subtitle: Text(admin['phone'] ?? 'بدون رقم', style: const TextStyle(color: Colors.grey)),
trailing: Text(
admin['created_at']?.split(' ')[0] ?? '',
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
),
const Divider(color: Color(0xFF1F2D4A), height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _handleAction(admin['id'], 'rejected'),
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
child: const Text('رفض'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => _handleAction(admin['id'], 'approved'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00E5FF),
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('موافقة', style: TextStyle(fontWeight: FontWeight.bold)),
),
],
),
],
),
);
},
);
}
}