294 lines
9.7 KiB
Dart
294 lines
9.7 KiB
Dart
import 'dart:convert';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:siro_driver/constant/box_name.dart';
|
||
import 'package:siro_driver/constant/links.dart';
|
||
import 'package:siro_driver/controller/functions/crud.dart';
|
||
import 'package:siro_driver/models/model/driver/rides_summary_model.dart';
|
||
import '../../../main.dart';
|
||
|
||
class StatisticsController extends GetxController {
|
||
bool isLoading = false;
|
||
|
||
// ═══ Weekly Data ═══
|
||
List<DayStat> weeklyStats = [];
|
||
double weeklyEarnings = 0;
|
||
int weeklyTrips = 0;
|
||
double weeklyHours = 0;
|
||
|
||
// ═══ Monthly Data ═══
|
||
List<MonthlyPriceDriverModel> monthlyEarnings = [];
|
||
List<MonthlyRideModel> monthlyRides = [];
|
||
List<MonthlyDataModel> monthlyDuration = [];
|
||
double monthlyTotalEarnings = 0;
|
||
int monthlyTotalTrips = 0;
|
||
double monthlyTotalHours = 0;
|
||
String bestDay = '--';
|
||
double bestDayEarnings = 0;
|
||
|
||
// ═══ Tab State ═══
|
||
int selectedTab = 0; // 0=weekly, 1=monthly
|
||
|
||
@override
|
||
void onInit() {
|
||
super.onInit();
|
||
reloadData();
|
||
}
|
||
|
||
void changeTab(int tab) {
|
||
selectedTab = tab;
|
||
update();
|
||
}
|
||
|
||
// ═══════ جلب البيانات الأسبوعية ═══════
|
||
Future<void> fetchWeeklyData() async {
|
||
isLoading = true;
|
||
update();
|
||
|
||
try {
|
||
var res = await CRUD().get(
|
||
link: AppLink.getWeeklyAggregate,
|
||
payload: {
|
||
'driver_id': box.read(BoxName.driverID).toString(),
|
||
},
|
||
);
|
||
|
||
if (res != null && res != 'failure') {
|
||
box.write('stats_weekly_cache', res);
|
||
var data = jsonDecode(res);
|
||
if (data['message'] is List) {
|
||
weeklyStats = (data['message'] as List)
|
||
.map((e) => DayStat.fromJson(e))
|
||
.toList();
|
||
weeklyEarnings = weeklyStats.fold(0, (s, d) => s + d.earnings);
|
||
weeklyTrips = weeklyStats.fold(0, (s, d) => s + d.trips);
|
||
weeklyHours = weeklyStats.fold(0, (s, d) => s + d.hours);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
debugPrint('❌ [Stats] Weekly fetch error: $e');
|
||
// Fallback: generate from local data
|
||
_generateLocalWeeklyData();
|
||
}
|
||
|
||
isLoading = false;
|
||
update();
|
||
}
|
||
|
||
// ═══════ جلب البيانات الشهرية ═══════
|
||
Future<void> fetchMonthlyData() async {
|
||
try {
|
||
// 1. أرباح شهرية
|
||
var earningsRes = await CRUD().getWallet(
|
||
link: AppLink.getAllPaymentFromRide,
|
||
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
||
);
|
||
if (earningsRes != null && earningsRes != 'failure') {
|
||
box.write('stats_monthly_earnings_cache', earningsRes);
|
||
var data = jsonDecode(earningsRes);
|
||
if (data['message'] is List) {
|
||
monthlyEarnings = (data['message'] as List)
|
||
.map((e) => MonthlyPriceDriverModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalEarnings =
|
||
monthlyEarnings.fold(0, (s, d) => s + d.pricePerDay);
|
||
|
||
// أفضل يوم
|
||
if (monthlyEarnings.isNotEmpty) {
|
||
var best = monthlyEarnings
|
||
.reduce((a, b) => a.pricePerDay > b.pricePerDay ? a : b);
|
||
bestDay = best.day.toString();
|
||
bestDayEarnings = best.pricePerDay;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. رحلات شهرية
|
||
var ridesRes = await CRUD().get(
|
||
link: AppLink.getRidesDriverByDay,
|
||
payload: {'driver_id': box.read(BoxName.driverID).toString()},
|
||
);
|
||
if (ridesRes != null && ridesRes != 'failure') {
|
||
box.write('stats_monthly_rides_cache', ridesRes);
|
||
var data = jsonDecode(ridesRes);
|
||
if (data['message'] is List) {
|
||
monthlyRides = (data['message'] as List)
|
||
.map((e) => MonthlyRideModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalTrips = monthlyRides.fold(0, (s, d) => s + d.countRide);
|
||
}
|
||
}
|
||
|
||
// 3. ساعات شهرية
|
||
var durationRes = await CRUD().get(
|
||
link: AppLink.getTotalDriverDuration,
|
||
payload: {'driver_id': box.read(BoxName.driverID).toString()},
|
||
);
|
||
if (durationRes != null && durationRes != 'failure') {
|
||
box.write('stats_monthly_duration_cache', durationRes);
|
||
var data = jsonDecode(durationRes);
|
||
if (data['message'] is List) {
|
||
monthlyDuration = (data['message'] as List)
|
||
.map((e) => MonthlyDataModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalHours =
|
||
monthlyDuration.fold(0, (s, d) => s + d.totalDuration.toDouble());
|
||
}
|
||
}
|
||
} catch (e) {
|
||
debugPrint('❌ [Stats] Monthly fetch error: $e');
|
||
}
|
||
|
||
update();
|
||
}
|
||
|
||
void _generateLocalWeeklyData() {
|
||
// Fallback بيانات محلية عند عدم توفر الـ API
|
||
final now = DateTime.now();
|
||
weeklyStats = List.generate(7, (i) {
|
||
final day = now.subtract(Duration(days: 6 - i));
|
||
return DayStat(
|
||
date: day,
|
||
dayName: _getDayName(day.weekday),
|
||
earnings: 0,
|
||
trips: 0,
|
||
hours: 0,
|
||
);
|
||
});
|
||
}
|
||
|
||
String _getDayName(int weekday) {
|
||
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||
return days[weekday - 1];
|
||
}
|
||
|
||
bool _isFetching = false;
|
||
|
||
Future<void> reloadData({bool force = false}) async {
|
||
if (_isFetching) return;
|
||
_isFetching = true;
|
||
|
||
// التحقق من التخزين المؤقت (3 ساعات)
|
||
String? lastFetchStr = box.read('stats_last_fetch');
|
||
if (!force && lastFetchStr != null) {
|
||
try {
|
||
DateTime lastFetch = DateTime.parse(lastFetchStr);
|
||
if (DateTime.now().difference(lastFetch).inHours < 3) {
|
||
debugPrint('ℹ️ [Statistics] Loading cached statistics (last fetch: $lastFetchStr)');
|
||
|
||
// تحميل الكاش الأسبوعي
|
||
String? weeklyCache = box.read('stats_weekly_cache');
|
||
if (weeklyCache != null) {
|
||
var data = jsonDecode(weeklyCache);
|
||
if (data['message'] is List) {
|
||
weeklyStats = (data['message'] as List)
|
||
.map((e) => DayStat.fromJson(e))
|
||
.toList();
|
||
weeklyEarnings = weeklyStats.fold(0, (s, d) => s + d.earnings);
|
||
weeklyTrips = weeklyStats.fold(0, (s, d) => s + d.trips);
|
||
weeklyHours = weeklyStats.fold(0, (s, d) => s + d.hours);
|
||
}
|
||
}
|
||
|
||
// تحميل الكاش الشهري (الأرباح)
|
||
String? earningsCache = box.read('stats_monthly_earnings_cache');
|
||
if (earningsCache != null) {
|
||
var data = jsonDecode(earningsCache);
|
||
if (data['message'] is List) {
|
||
monthlyEarnings = (data['message'] as List)
|
||
.map((e) => MonthlyPriceDriverModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalEarnings =
|
||
monthlyEarnings.fold(0, (s, d) => s + d.pricePerDay);
|
||
if (monthlyEarnings.isNotEmpty) {
|
||
var best = monthlyEarnings
|
||
.reduce((a, b) => a.pricePerDay > b.pricePerDay ? a : b);
|
||
bestDay = best.day.toString();
|
||
bestDayEarnings = best.pricePerDay;
|
||
}
|
||
}
|
||
}
|
||
|
||
// تحميل الكاش الشهري (الرحلات)
|
||
String? ridesCache = box.read('stats_monthly_rides_cache');
|
||
if (ridesCache != null) {
|
||
var data = jsonDecode(ridesCache);
|
||
if (data['message'] is List) {
|
||
monthlyRides = (data['message'] as List)
|
||
.map((e) => MonthlyRideModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalTrips = monthlyRides.fold(0, (s, d) => s + d.countRide);
|
||
}
|
||
}
|
||
|
||
// تحميل الكاش الشهري (المدد)
|
||
String? durationCache = box.read('stats_monthly_duration_cache');
|
||
if (durationCache != null) {
|
||
var data = jsonDecode(durationCache);
|
||
if (data['message'] is List) {
|
||
monthlyDuration = (data['message'] as List)
|
||
.map((e) => MonthlyDataModel.fromJson(e))
|
||
.toList();
|
||
monthlyTotalHours =
|
||
monthlyDuration.fold(0, (s, d) => s + d.totalDuration.toDouble());
|
||
}
|
||
}
|
||
|
||
_isFetching = false;
|
||
update();
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
debugPrint('⚠️ [Statistics] Failed to load cached statistics: $e');
|
||
}
|
||
}
|
||
|
||
isLoading = true;
|
||
update();
|
||
|
||
try {
|
||
debugPrint('📊 [Statistics] Reloading data from server...');
|
||
await fetchWeeklyData();
|
||
await fetchMonthlyData();
|
||
box.write('stats_last_fetch', DateTime.now().toIso8601String());
|
||
debugPrint('📊 [Statistics] Data reload complete.');
|
||
} catch (e) {
|
||
debugPrint('❌ [Statistics] Error reloading data: $e');
|
||
} finally {
|
||
_isFetching = false;
|
||
isLoading = false;
|
||
update();
|
||
}
|
||
}
|
||
}
|
||
|
||
// ═══════ نموذج إحصائية اليوم ═══════
|
||
class DayStat {
|
||
final DateTime date;
|
||
final String dayName;
|
||
final double earnings;
|
||
final int trips;
|
||
final double hours;
|
||
|
||
DayStat({
|
||
required this.date,
|
||
required this.dayName,
|
||
required this.earnings,
|
||
required this.trips,
|
||
required this.hours,
|
||
});
|
||
|
||
factory DayStat.fromJson(Map<String, dynamic> json) {
|
||
final date =
|
||
DateTime.tryParse(json['day']?.toString() ?? '') ?? DateTime.now();
|
||
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||
return DayStat(
|
||
date: date,
|
||
dayName: dayNames[date.weekday - 1],
|
||
earnings: double.tryParse(json['earnings']?.toString() ?? '0') ?? 0,
|
||
trips: int.tryParse(json['trips']?.toString() ?? '0') ?? 0,
|
||
hours: double.tryParse(json['hours']?.toString() ?? '0') ?? 0,
|
||
);
|
||
}
|
||
}
|