import 'dart:convert'; import 'package:get/get.dart'; import '../../constant/links.dart'; import '../../views/widgets/error_snakbar.dart'; import '../functions/crud.dart'; /// حجوزات الراكب المسبقة. /// /// الحجز ليس رحلة: يسجَّل كنيّة سفر، ويحوّله الخادم لرحلة فعلية قبل /// الموعد بهامش يشتقّه من المسافة. لذلك لا شاشة تتبّع هنا — الرحلة /// تظهر في الخريطة كأي رحلة حين يحين وقتها. class ScheduledRidesController extends GetxController { final CRUD _crud = CRUD(); final bookings = >[].obs; final isLoading = false.obs; final isSaving = false.obs; /// حدود الخادم — مكرّرة هنا للتحقق المبكر فقط. الخادم هو من يفرضها. static const int minLeadMinutes = 30; static const int maxDaysAhead = 2; DateTime get earliest => DateTime.now().add(const Duration(minutes: minLeadMinutes)); DateTime get latest => DateTime.now().add(const Duration(days: maxDaysAhead)); @override void onInit() { super.onInit(); fetch(); } Future fetch() async { isLoading.value = true; try { final res = await _crud.post( link: AppLink.scheduledList, payload: {'scope': 'upcoming'}, ); final data = _extract(res); final list = (data?['bookings'] as List?) ?? []; bookings.assignAll( list.map((e) => Map.from(e as Map)).toList()); } catch (e) { mySnackbarError('تعذّر جلب الحجوزات'); } finally { isLoading.value = false; } } /// ينشئ حجزاً. يرجع true عند النجاح. /// /// [when] موعد الانطلاق كما اختاره الراكب. Future create({ required String startLocation, required String endLocation, required String startName, required String endName, required String carType, required double distanceKm, required int durationSeconds, required double estimatedPrice, required DateTime when, String? note, }) async { if (isSaving.value) return false; // ‏فحص محلي للراحة — يوفّر رحلة للخادم ويعطي رسالة أوضح. if (when.isBefore(earliest)) { mySnackbarError('اختر موعداً بعد $minLeadMinutes دقيقة على الأقل'); return false; } if (when.isAfter(latest)) { mySnackbarError('يمكنك الحجز حتى $maxDaysAhead يومين مقدماً فقط'); return false; } isSaving.value = true; try { final res = await _crud.post( link: AppLink.scheduledAdd, payload: { 'start_location': startLocation, 'end_location': endLocation, 'start_name': startName, 'end_name': endName, 'car_type': carType, 'distance': distanceKm.toString(), 'duration': durationSeconds.toString(), 'estimated_price': estimatedPrice.toString(), 'scheduled_at': _fmt(when), if (note != null && note.trim().isNotEmpty) 'note': note.trim(), }, ); final data = _extract(res); if (data == null) return false; await fetch(); return true; } catch (e) { mySnackbarError('تعذّر الاتصال بالخادم'); return false; } finally { isSaving.value = false; } } Future cancel(int id) async { try { final res = await _crud.post( link: AppLink.scheduledCancel, payload: {'id': id.toString()}, ); if (_extract(res) == null) return; mySnackbarSuccess('أُلغي الحجز'); await fetch(); } catch (e) { mySnackbarError('تعذّر إلغاء الحجز'); } } String _fmt(DateTime d) => '${d.year}-${_2(d.month)}-${_2(d.day)} ${_2(d.hour)}:${_2(d.minute)}:00'; String _2(int n) => n.toString().padLeft(2, '0'); Map? _extract(dynamic res) { final decoded = res is String ? jsonDecode(res) : res; if (decoded is! Map) { mySnackbarError('رد غير مفهوم من الخادم'); return null; } if (decoded['status'] != 'success') { // ‏رسالة الخادم أدقّ من أي نص عام: تفرّق بين "لديك حجز قريب" و // ‏"الموعد خارج المدى المسموح". mySnackbarError(decoded['message']?.toString() ?? 'فشلت العملية'); return null; } final data = decoded['message'] ?? decoded['data']; return data is Map ? Map.from(data) : {}; } }