Update codebase
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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 = <Map<String, dynamic>>[].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<void> 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<String, dynamic>.from(e as Map)).toList());
|
||||
} catch (e) {
|
||||
mySnackbarError('تعذّر جلب الحجوزات');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// ينشئ حجزاً. يرجع true عند النجاح.
|
||||
///
|
||||
/// [when] موعد الانطلاق كما اختاره الراكب.
|
||||
Future<bool> 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<void> 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<String, dynamic>? _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<String, dynamic>.from(data) : <String, dynamic>{};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user