قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
3.9 KiB
Dart
144 lines
3.9 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import '../../constant/links.dart';
|
|
import '../functions/crud.dart';
|
|
|
|
class RideLookupController extends GetxController {
|
|
final TextEditingController phoneCtrl = TextEditingController();
|
|
|
|
bool isLoading = false;
|
|
Map<String, dynamic>? passenger; // {id, first_name, last_name, phone}
|
|
Map<String, dynamic>? ride; // Ride details
|
|
|
|
// Status filter for the search tab
|
|
String currentStatusFilter = '';
|
|
|
|
// Whitelist of allowed statuses for the Update Dropdown
|
|
// مطابقة لحالات الرحلة الفعلية في قاعدة البيانات
|
|
final List<String> statusOptions = const [
|
|
'New', // جديد - بانتظار سائق
|
|
'waiting', // في انتظار سائق
|
|
'Apply', // سائق قبل الرحلة
|
|
'Arrived', // السائق وصل
|
|
'Begin', // الرحلة بدأت
|
|
'Finished', // مكتملة
|
|
'Cancel', // إلغاء
|
|
];
|
|
|
|
String? selectedStatus;
|
|
|
|
// Hydrate dropdown value from the current ride data
|
|
void hydrateSelectedFromRide() {
|
|
final cur = (ride?['status'] ?? '') as String;
|
|
selectedStatus = statusOptions.contains(cur) ? cur : null;
|
|
update();
|
|
}
|
|
|
|
Future<bool> updateRideStatus({String? note}) async {
|
|
if (ride == null) return false;
|
|
if (selectedStatus == null || selectedStatus!.isEmpty) return false;
|
|
|
|
isLoading = true;
|
|
update();
|
|
|
|
try {
|
|
final res = await CRUD().post(
|
|
link: AppLink.admin_update_ride_status,
|
|
payload: {
|
|
'id': "${ride!['id']}",
|
|
'status': selectedStatus!,
|
|
if (note != null && note.trim().isNotEmpty) 'reason': note.trim(),
|
|
},
|
|
);
|
|
|
|
final d = jsonDecode(res);
|
|
final ok = (d['status'] == 'success');
|
|
|
|
if (ok) {
|
|
// Update local ride details from response
|
|
final updated = (d['message'] ?? d)['ride'];
|
|
if (updated != null) {
|
|
ride = Map<String, dynamic>.from(updated);
|
|
}
|
|
update();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} catch (_) {
|
|
return false;
|
|
} finally {
|
|
isLoading = false;
|
|
update();
|
|
}
|
|
}
|
|
|
|
// Updated to accept status filter
|
|
Future<bool> searchLatest({String? status}) async {
|
|
final phone = phoneCtrl.text.trim();
|
|
|
|
// If status is passed, update the current filter
|
|
if (status != null) {
|
|
currentStatusFilter = status;
|
|
}
|
|
|
|
// If phone is empty, we stop unless your API supports fetching "latest of all users"
|
|
if (phone.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
isLoading = true;
|
|
update();
|
|
|
|
try {
|
|
final res = await CRUD().post(
|
|
link: AppLink.admin_get_rides_by_phone,
|
|
payload: {
|
|
'phone': phone,
|
|
// If filter is 'All', send empty string to PHP, otherwise send the exact status
|
|
'status': currentStatusFilter == 'All' ? '' : currentStatusFilter,
|
|
},
|
|
);
|
|
|
|
final d = res;
|
|
|
|
if (d['status'] == 'success') {
|
|
// يستجيب API الجديد بـ user_type, user, rides
|
|
final message = d['message'] ?? d;
|
|
passenger = message['user']; // user يمكن أن يكون سائقاً أو راكباً
|
|
ride = (message['rides'] is List && message['rides'].isNotEmpty)
|
|
? message['rides'][0]
|
|
: null;
|
|
|
|
// Hydrate the dropdown for the update section based on the fetched ride
|
|
hydrateSelectedFromRide();
|
|
|
|
update();
|
|
return true;
|
|
} else {
|
|
passenger = null;
|
|
ride = null;
|
|
update();
|
|
return false;
|
|
}
|
|
} catch (_) {
|
|
passenger = null;
|
|
ride = null;
|
|
update();
|
|
return false;
|
|
} finally {
|
|
isLoading = false;
|
|
update();
|
|
}
|
|
}
|
|
|
|
String rideHeader() {
|
|
if (ride == null) return '';
|
|
final id = ride!['id'] ?? '';
|
|
final st = (ride!['status'] ?? '—').toString();
|
|
return "Ride #$id — $st";
|
|
}
|
|
}
|