chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل

نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق».
نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا
`cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules ·
.dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore.

هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً
مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ.

⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا
صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم
توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:10:29 +03:00
co-authored by Claude Opus 5
commit 92dc6b3641
3654 changed files with 896478 additions and 0 deletions
@@ -0,0 +1,75 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class AnalyticsV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> growthData = {};
Map<String, dynamic> revenueData = {};
List<dynamic> topDrivers = [];
@override
void onInit() {
super.onInit();
fetchAllAnalytics();
}
Future<void> fetchAllAnalytics() async {
isLoading = true;
update();
await Future.wait([
fetchGrowth(),
fetchRevenue(),
fetchDriverRanking(),
]);
isLoading = false;
update();
}
Future<void> fetchGrowth() async {
try {
var res = await CRUD().get(link: AppLink.growthV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
growthData = d['data'];
}
}
} catch (e) {
Log.print('Error fetching growth analytics: $e');
}
}
Future<void> fetchRevenue() async {
try {
var res = await CRUD().get(link: AppLink.revenueV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
revenueData = d['data'];
}
}
} catch (e) {
Log.print('Error fetching revenue analytics: $e');
}
}
Future<void> fetchDriverRanking() async {
try {
var res = await CRUD().get(link: AppLink.driverRankingV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
topDrivers = d['data'];
}
}
} catch (e) {
Log.print('Error fetching driver ranking: $e');
}
}
}
@@ -0,0 +1,139 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class CaptainAdminController extends GetxController {
bool isLoading = false;
Map captainData = {};
Map captain = {};
final captainController = TextEditingController();
final captainPrizeController = TextEditingController();
final titleNotify = TextEditingController();
final bodyNotify = TextEditingController();
final formCaptainKey = GlobalKey<FormState>();
final formCaptainPrizeKey = GlobalKey<FormState>();
Future getCaptainCount() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getCaptainDetails, payload: {});
var d = jsonDecode(res);
if (d['status'] == 'success') {
captainData = d;
}
isLoading = false;
update();
}
Future deletCaptain() async {
isLoading = true;
update();
var res = await CRUD().get(
link: AppLink.deleteCaptain,
payload: {},
);
var d = jsonDecode(res);
if (d['status'] == 'success') {
captainData = d;
}
isLoading = false;
update();
}
Future find_driver_by_phone(String phone) async {
isLoading = true;
update();
var res = await CRUD().post(
link: AppLink.find_driver_by_phone,
payload: {'phone': "963$phone"},
);
var d = (res);
if (d != 'failure') {
captainData = d;
} else {
captainData = {};
mySnackbarError('No captain found with this phone number');
}
isLoading = false;
update();
}
Future addCaptainPrizeToWallet() async {
String? paymentId;
//todo link to add wallet to captain
for (var i = 0; i < captainData['message'].length; i++) {
await CRUD().post(link: AppLink.addDriverPaymentPoints, payload: {
'driverID': captainData['message'][i]['id'],
'amount': captainPrizeController.text,
'paymentMethod': 'Prize',
}).then((value) {
paymentId = value['message'].toString();
});
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
'driverID': captainData['message'][i]['id'],
'amount': captainPrizeController.text,
'paymentMethod': 'Prize',
'paymentID': paymentId.toString(),
});
}
Get.back();
}
void addCaptainsPrizeToWalletSecure() async {
try {
// Check if local authentication is available
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
);
if (didAuthenticate) {
// User authenticated successfully, proceed with payment
await addCaptainPrizeToWallet();
mySnackbarSuccess('Prize Added');
} else {
// Authentication failed, handle accordingly
mySnackbarError('Authentication failed');
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addCaptainPrizeToWallet();
mySnackbarSuccess('Prize Added');
}
} catch (e) {
rethrow;
}
}
Future getCaptains() async {
var res = await CRUD()
.get(link: AppLink.getCaptainDetailsByEmailOrIDOrPhone, payload: {
'driver_id': captainController.text,
'driverEmail': captainController.text,
'driverPhone': captainController.text,
});
var d = jsonDecode(res);
if (d['status'] == 'success') {
captain = d;
}
update();
}
@override
void onInit() {
getCaptainCount();
super.onInit();
}
}
@@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class ComplaintController extends GetxController {
var complaintList = [].obs;
var isLoading = false.obs;
var showOnlyDelayed = false.obs;
final CRUD _crud = CRUD();
List<dynamic> get delayedComplaints {
final weekAgo = DateTime.now().subtract(const Duration(days: 7));
return complaintList.where((c) {
if (c['statusComplaint'] == 'Resolved') return false;
try {
final date = DateTime.parse(c['date_filed']);
return date.isBefore(weekAgo);
} catch (e) {
return false;
}
}).toList();
}
@override
void onInit() {
super.onInit();
getComplaints();
}
Future<void> getComplaints() async {
isLoading.value = true;
try {
var response = await _crud.get(link: AppLink.getComplaintAllData);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
complaintList.assignAll(decoded['message']);
}
} else {
complaintList.clear();
}
} catch (e) {
mySnackbarError("فشل جلب الشكاوى: $e");
} finally {
isLoading.value = false;
}
}
Future<bool> updateComplaintStatus(String id, String status, String resolution) async {
isLoading.value = true;
try {
var response = await _crud.post(link: "${AppLink.server}/serviceapp/update_complaint.php", payload: {
"id": id,
"statusComplaint": status,
"resolution": resolution,
});
if (response != null && response is Map && response['status'] == "success") {
await getComplaints();
return true;
}
return false;
} catch (e) {
mySnackbarError("فشل تحديث الشكوى: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/auth/otp_helper.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class DashboardController extends GetxController {
bool isLoading = false;
List dashbord = [];
String creditSMS = '0';
final formKey = GlobalKey<FormState>();
final smsText = TextEditingController();
Future getDashBoard() async {
isLoading = true;
update();
// 🔹 Request main dashboard data
var res = await CRUD().get(link: AppLink.getdashbord, payload: {});
print('📡 Main dashboard response: $res');
if (res == 'token_expired') {
print('❌ Admin token expired. Attempting seamless auto-login.');
box.remove(BoxName.jwt);
try {
final otpHelper = Get.put(OtpHelper());
await otpHelper.checkAdminLogin();
} catch (e) {
Get.offAllNamed('/login');
}
return;
}
if (res != 'failure' && res != null) {
try {
var d = res is String ? jsonDecode(res) : res;
print('✅ Decoded main dashboard: ${jsonEncode(d)}');
if (d['status'] == 'success' && d['message'] != null) {
dashbord = d['message'] is List ? d['message'] : [d['message']];
}
} catch (e) {
print('❌ Error parsing main dashboard: $e');
}
} else {
print('❌ Failed to load main dashboard');
}
// 🔹 Request wallet dashboard data
var resPayments = await CRUD().postWallet(
link: AppLink.getPaymentsDashboard,
payload: {},
);
print('💳 Wallet dashboard response: $resPayments');
if (resPayments is Map && resPayments['status'] == 'success') {
var p = resPayments;
print('✅ Decoded wallet dashboard: ${jsonEncode(p)}');
if (dashbord.isNotEmpty &&
p['message'] is List &&
p['message'].isNotEmpty) {
dashbord[0].addAll(p['message'][0]);
} else if (dashbord.isNotEmpty && p['message'] is Map) {
dashbord[0].addAll(p['message']);
}
} else {
print('❌ Failed to load wallet dashboard (or verification required)');
}
// 🔹 Check SMS credit
// var res2 = await CRUD().kazumiSMS(
// link: 'https://sms.kazumi.me/api/sms/check-credit',
// payload: {"username": "Sefer", "password": AK.smsPasswordEgypt},
// );
// creditSMS = res2['credit'];
// print('📱 SMS Credit Response: ${jsonEncode(res2)}');
// print('💰 creditSMS: $creditSMS');
isLoading = false;
update();
}
// sendSMSMethod() async {
// if (formKey.currentState!.validate()) {
// for (var phoneNumber in box.read(BoxName.tokensDrivers)['message']) {
// // for (var i = 0; i < 2; i++) {
// await CRUD().sendSmsEgypt(
// phoneNumber['phone'].toString(),
// // box.read(BoxName.tokensDrivers)['message'][i]['phone'].toString(),
// smsText.text,
// );
// // print('CRUD().phoneDriversTest.: ${phoneNumber['phone']}');
// Future.delayed(const Duration(microseconds: 20));
// }
// Get.back();
// }
// }
@override
void onInit() async {
getDashBoard();
super.onInit();
}
}
@@ -0,0 +1,62 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class DashboardV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> realtimeData = {};
List<dynamic> smartAlerts = [];
Timer? _timer;
@override
void onInit() {
super.onInit();
fetchRealtimeData();
fetchSmartAlerts();
// Auto refresh every 2 minutes
_timer = Timer.periodic(const Duration(minutes: 2), (timer) {
fetchRealtimeData();
fetchSmartAlerts();
});
}
@override
void onClose() {
_timer?.cancel();
super.onClose();
}
Future<void> fetchRealtimeData() async {
try {
var res = await CRUD().get(link: AppLink.realtimeDashboardV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
realtimeData = d['message'];
isLoading = false;
update();
}
}
} catch (e) {
Log.print('Error fetching realtime dashboard: $e');
}
}
Future<void> fetchSmartAlerts() async {
try {
var res = await CRUD().get(link: AppLink.smartAlertsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
smartAlerts = d['message'];
update();
}
}
} catch (e) {
Log.print('Error fetching smart alerts: $e');
}
}
}
@@ -0,0 +1,98 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class DriverDocsController extends GetxController {
var pendingDrivers = [].obs;
var isLoading = false.obs;
var isMoreLoading = false.obs;
var hasMore = true.obs;
int _offset = 0;
final int _limit = 10;
final CRUD _crud = CRUD();
@override
void onInit() {
super.onInit();
getPendingDrivers();
}
Future<void> getPendingDrivers({bool refresh = true}) async {
if (refresh) {
isLoading.value = true;
_offset = 0;
hasMore.value = true;
} else {
if (isMoreLoading.value || !hasMore.value) return;
isMoreLoading.value = true;
}
try {
var response = await _crud.post(
link: AppLink.getDriversPending,
payload: {"limit": _limit.toString(), "offset": _offset.toString()},
);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
List newItems = decoded['message'] ?? [];
if (refresh) {
pendingDrivers.assignAll(newItems);
} else {
pendingDrivers.addAll(newItems);
}
_offset += newItems.length;
if (newItems.length < _limit) {
hasMore.value = false;
}
}
}
} catch (e) {
mySnackbarError("فشل جلب السائقين: $e");
} finally {
isLoading.value = false;
isMoreLoading.value = false;
}
}
Future<void> loadMore() async {
await getPendingDrivers(refresh: false);
}
Future<Map<String, dynamic>?> getDriverFullDetails(String id) async {
try {
var response = await _crud.get(link: "${AppLink.getDriverDetails}?id=$id");
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
return decoded['data'];
}
}
} catch (e) {
mySnackbarError("فشل جلب تفاصيل السائق: $e");
}
return null;
}
Future<bool> approveDriver(String id) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.updateDriverFromAdmin, payload: {
"id": id,
"status": "active",
});
if (response != null && response is Map && response['status'] == "success") {
await getPendingDrivers();
return true;
}
return false;
} catch (e) {
mySnackbarError("فشل اعتماد السائق: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,61 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class FinancialV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> stats = {};
List<dynamic> settlements = [];
@override
void onInit() {
super.onInit();
fetchAllFinancials();
}
Future<void> fetchAllFinancials() async {
isLoading = true;
update();
await Future.wait([
fetchStats(),
fetchSettlements(),
]);
isLoading = false;
update();
}
Future<void> fetchStats() async {
try {
var res =
await CRUD().getWallet(link: AppLink.financialStatsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
stats = d['data'];
}
}
} catch (e) {
Log.print('Error fetching financial stats: $e');
}
}
Future<void> fetchSettlements() async {
try {
var res =
await CRUD().getWallet(link: AppLink.settlementsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
settlements = d['data'];
}
}
} catch (e) {
Log.print('Error fetching settlements: $e');
}
}
}
@@ -0,0 +1,25 @@
class InvoiceModel {
final String invoiceNumber;
final String amount;
final String date;
final String name;
final String? imageLink;
InvoiceModel({
required this.invoiceNumber,
required this.amount,
required this.date,
required this.name,
this.imageLink,
});
factory InvoiceModel.fromJson(Map<String, dynamic> json) {
return InvoiceModel(
invoiceNumber: json['invoice_number'],
amount: json['amount'].toString(),
date: json['date'],
name: json['name'],
imageLink: json['image_link'],
);
}
}
@@ -0,0 +1,90 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class KazanController extends GetxController {
var kazanData = {}.obs;
var isLoading = false.obs;
var selectedCountry = 'سوريا'.obs;
final CRUD _crud = CRUD();
final List<Map<String, String>> countries = [
{'code': 'Syria', 'name': 'سوريا', 'flag': '🇸🇾'},
{'code': 'Jordan', 'name': 'الأردن', 'flag': '🇯🇴'},
{'code': 'Egypt', 'name': 'مصر', 'flag': '🇪🇬'},
];
String get selectedCountryCode =>
countries.firstWhere(
(c) => c['name'] == selectedCountry.value,
orElse: () => countries.first,
)['code']!;
@override
void onInit() {
super.onInit();
getKazan();
}
void setCountry(String countryName) {
selectedCountry.value = countryName;
getKazan();
}
Future<void> getKazan() async {
isLoading.value = true;
try {
final countryParam = selectedCountryCode;
var response = await _crud.get(link: "${AppLink.getKazanPercent}?country=$countryParam");
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
var message = decoded['message'];
if (message is List && message.isNotEmpty) {
final match = message.cast<Map<String, dynamic>>().firstWhere(
(row) =>
(row['country'] ?? '').toString().toLowerCase() ==
countryParam.toLowerCase(),
orElse: () => message[0] as Map<String, dynamic>,
);
kazanData.value = Map<String, dynamic>.from(match);
kazanData['country'] = selectedCountry.value;
} else {
kazanData.value = {'country': selectedCountry.value};
}
}
}
} catch (e) {
mySnackbarError('فشل جلب بيانات التسعير: $e');
} finally {
isLoading.value = false;
}
}
Future<bool> updateKazan(Map<String, dynamic> data) async {
isLoading.value = true;
try {
data['country'] = selectedCountryCode;
final String link = data.containsKey('id') ? AppLink.updateKazanPercent : AppLink.addKazanPercent;
Map<String, String> payload = {};
data.forEach((key, value) {
payload[key] = value.toString();
});
var response = await _crud.post(link: link, payload: payload);
if (response != null && response is Map && response['status'] == "success") {
await getKazan();
return true;
}
return false;
} catch (e) {
mySnackbarError('فشل تحديث التسعير: $e');
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,75 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class LiveAnalyticsController extends GetxController {
bool isLoading = true;
String selectedDate = '';
Map<String, dynamic> allData = {};
Map<String, dynamic> get realtime => _section('realtime');
Map<String, dynamic> get gap => _section('gap');
Map<String, dynamic> get heatmap => _section('heatmap');
Map<String, dynamic> get pricing => _section('pricing');
Map<String, dynamic> get revenue => _section('revenue');
Map<String, dynamic> get growth => _section('growth');
Map<String, dynamic> get market => _section('market');
Map<String, dynamic> get complaints => _section('complaints');
Map<String, dynamic> get funnel => _section('funnel');
Map<String, dynamic> get hourly => _section('hourly');
Map<String, dynamic> get weekly => _section('weekly');
Map<String, dynamic> get zones => _section('zones');
Map<String, dynamic> get retention => _section('retention');
Map<String, dynamic> get competitor => _section('competitor');
List<String> availableDates = [];
Map<String, dynamic> _section(String key) {
final d = allData[key];
if (d is Map<String, dynamic>) return d;
return {};
}
@override
void onInit() {
super.onInit();
final now = DateTime.now();
selectedDate =
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
fetchAll();
}
Future<void> fetchAll() async {
isLoading = true;
update();
try {
var res = await CRUD().get(
link: AppLink.analyticsDashboardData,
payload: {'date': selectedDate, 'section': 'all'},
);
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success' && d['data'] != null) {
allData = Map<String, dynamic>.from(d['data']);
availableDates =
List<String>.from(d['available_dates'] ?? []);
}
}
} catch (e) {
Log.print('Error fetching live analytics: $e');
}
isLoading = false;
update();
}
void changeDate(String date) {
selectedDate = date;
fetchAll();
}
}
@@ -0,0 +1,336 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class MarketingController extends GetxController {
bool isLoading = false;
List priceAnomalies = [];
List campaignsLog = [];
// --- Dynamic Dashboard Configurations ---
String selectedCountry = 'All'; // 'All', 'SY', 'JO', 'EG', 'IQ'
bool isAutopilotEnabled = true;
String systemPrompt = "أنت المحلل الذكي لتطبيق Siro لخدمات نقل الركاب. قمنا برصد قراءات أسعار المنافسين لتقديم عروض وحملات تسويقية ملائمة ومخصصة.";
// --- AI API Token & Cost Telemetry ---
int apiRequestsCount = 0;
int totalTokensUsed = 0;
double estimatedCostUSD = 0.0;
// --- Price Comparison Data ---
List hourlyPriceData = [];
List pciRegions = [];
Map siroBasePrices = {};
// --- Country Selection Handler ---
void changeCountry(String countryCode) {
selectedCountry = countryCode;
fetchAnomalies();
fetchCampaignsLog();
fetchPriceComparison();
fetchPriceGapHeatmap();
fetchMarketShareAnalytics();
fetchAiPricePrediction();
fetchWinbackTargets();
}
// --- Autopilot Status Toggle ---
void toggleAutopilot(bool value) {
isAutopilotEnabled = value;
update();
mySnackbarInfo(value ? "Full Autopilot mode enabled".tr : "Approval Mode enabled".tr);
}
// --- System Prompt Configuration Saver ---
void savePrompt(String newPrompt) {
systemPrompt = newPrompt;
update();
mySnackbarSuccess("Gemini system instructions updated successfully".tr);
}
Future<void> fetchAnomalies() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getMarketAnomalies,
payload: params,
);
if (res is Map && res['status'] == 'success') {
priceAnomalies = res['message'] ?? [];
} else {
mySnackbarError("Failed to fetch price anomalies");
}
} catch (e) {
mySnackbarError("Network error while loading anomalies");
} finally {
isLoading = false;
update();
}
}
Future<void> fetchTelemetry() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getTelemetry,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
apiRequestsCount = data['api_requests_count'] ?? 0;
totalTokensUsed = data['total_tokens_used'] ?? 0;
estimatedCostUSD = (data['estimated_cost_usd'] ?? 0.0).toDouble();
update();
}
}
} catch (e) {
// Silently fail — telemetry is non-critical
}
}
Future<void> fetchCampaignsLog() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getCampaignsLog,
payload: params,
);
if (res is Map && res['status'] == 'success') {
campaignsLog = res['message'] ?? [];
} else {
mySnackbarError("Failed to fetch campaign logs");
}
} catch (e) {
mySnackbarError("Network error while loading campaign logs");
} finally {
isLoading = false;
update();
}
}
Future<void> fetchPriceComparison() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getPriceComparison,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
hourlyPriceData = data['hourly_competitor_prices'] ?? [];
pciRegions = data['pci_regions'] ?? [];
siroBasePrices = data['siro_base_prices'] is Map
? data['siro_base_prices'] as Map
: {};
update();
}
}
} catch (e) {
// Silently fail
}
}
Future<void> triggerAICampaign() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.triggerCampaign,
payload: params,
);
if (res is Map) {
if (res['status'] == 'success') {
mySnackbarSuccess("AI campaign triggered successfully! Promos sent.");
fetchCampaignsLog();
fetchTelemetry();
} else {
mySnackbarWarning(res['message'] ?? "Campaign rate limited.");
}
} else {
mySnackbarError("Failed to trigger AI campaign");
}
} catch (e) {
mySnackbarError("Network error while triggering campaign");
} finally {
isLoading = false;
update();
}
}
// --- What-If Simulator State ---
double? simulatedPci;
double? simulatedMarketShare;
String? simulatorRecommendationStatus;
String? simulatorRecommendationMessage;
Future<void> runWhatIfSimulation(String proposedSpeedPrice) async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {
'speed_price': proposedSpeedPrice
};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY'; // Fallback
}
var res = await CRUD().post(
link: AppLink.whatIfSimulator,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
simulatedPci = (data['simulated_pci'] ?? 0.0).toDouble();
simulatedMarketShare = (data['simulated_market_share_percent'] ?? 0.0).toDouble();
simulatorRecommendationStatus = data['recommendation_status'];
simulatorRecommendationMessage = data['recommendation_message'];
}
} else {
mySnackbarError(res['message'] ?? "Failed to run simulation");
}
} catch (e) {
mySnackbarError("Network error during simulation");
} finally {
isLoading = false;
update();
}
}
// --- Heatmap State ---
List heatmapData = [];
double currentSiroPriceHeatmap = 0.0;
Future<void> fetchPriceGapHeatmap() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY'; // Fallback
}
var res = await CRUD().post(
link: AppLink.getPriceGapHeatmap,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
heatmapData = data['heatmap_data'] ?? [];
currentSiroPriceHeatmap = (data['current_siro_price_per_km'] ?? 0.0).toDouble();
update();
}
}
} catch (e) {
// Silently fail
}
}
// --- Market Share Analytics ---
List marketShareData = [];
Future<void> fetchMarketShareAnalytics() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.getMarketShareAnalytics,
payload: params,
);
if (res is Map && res['status'] == 'success') {
marketShareData = res['historical_data'] ?? [];
update();
}
} catch (e) {
// Ignore
}
}
// --- AI Price Prediction ---
String? aiPredictionMessage;
List predictedSurgeHours = [];
Future<void> fetchAiPricePrediction() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.aiPricePrediction,
payload: params,
);
if (res is Map && res['status'] == 'success') {
aiPredictionMessage = res['ai_analysis_message'];
predictedSurgeHours = res['predicted_surge_hours'] ?? [];
update();
}
} catch (e) {
// Ignore
}
}
// --- Win-Back Targets ---
List winbackTargets = [];
int winbackTotalCount = 0;
Future<void> fetchWinbackTargets() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.winbackHotspotTargets,
payload: params,
);
if (res is Map && res['status'] == 'success') {
winbackTargets = res['targets'] ?? [];
winbackTotalCount = res['total_targets'] ?? 0;
update();
}
} catch (e) {
// Ignore
}
}
}
@@ -0,0 +1,169 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class PassengerAdminController extends GetxController {
bool isLoading = false;
Map passengersData = {};
Map passengers = {};
double height = 150;
final formPassKey = GlobalKey<FormState>();
final formPrizeKey = GlobalKey<FormState>();
final titleNotify = TextEditingController();
final bodyNotify = TextEditingController();
final passengerController = TextEditingController();
final passengerPrizeController = TextEditingController();
Future getPassengerCount() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getPassengerDetails, payload: {});
var d = jsonDecode(res);
if (d['status'] == 'success') {
passengersData = d;
}
isLoading = false;
update();
}
Future addPassengerPrizeToWallet() async {
for (var i = 0; i < passengersData['message'].length; i++) {
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
'passenger_id': passengersData['message'][i]['id'],
'balance': passengerPrizeController.text,
});
}
Get.back();
}
// داخل الـController نفسه
Future<bool> updatePassenger({
required String id, // أو مرّر phoneLookup بدل id لو حاب
String? firstName,
String? lastName,
String? phone,
}) async {
// لا نرسل طلب إذا ما في أي تغيير
if ((firstName == null || firstName.trim().isEmpty) &&
(lastName == null || lastName.trim().isEmpty) &&
(phone == null || phone.trim().isEmpty)) {
return false;
}
// فلتر بسيط للأرقام فقط
// String _normalizePhone(String s) => s.replaceAll(RegExp(r'\D+'), '');
final Map<String, dynamic> payload = {
'id':
id, // لو بدك تستخدم phone_lookup بدل id: احذف هذا وأرسل {'phone_lookup': phoneLookup}
};
if (firstName != null && firstName.trim().isNotEmpty) {
payload['first_name'] = firstName.trim();
}
if (lastName != null && lastName.trim().isNotEmpty) {
payload['last_name'] = lastName.trim();
}
if (phone != null && phone.trim().isNotEmpty) {
payload['phone'] = (phone);
}
// حالة تحميل
isLoading = true;
update();
try {
final res = await CRUD().post(
link: AppLink.admin_update_passenger, // عدّل الرابط حسب اسم مسارك
payload: payload,
);
final d = (res);
final ok = (d['status'] == 'success');
if (ok) {
// (اختياري) حدّث الكاش/الواجهة — مثلاً أعد الجلب
mySnackbarSuccess(d['message']?.toString() ?? 'Passenger updated successfully');
// await getPassengerCount(); // أو حدّث passengersData محليًا إذا متاح
} else {
// (اختياري) أظهر رسالة خطأ
// Get.snackbar('Update failed', d['message']?.toString() ?? 'Unknown error');
}
return ok;
} catch (e) {
// Get.snackbar('Error', e.toString());
return false;
} finally {
isLoading = false;
update();
}
}
void addPassengerPrizeToWalletSecure() async {
try {
// Check if local authentication is available
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
);
if (didAuthenticate) {
// User authenticated successfully, proceed with payment
await addPassengerPrizeToWallet();
mySnackbarSuccess('Prize Added');
} else {
// Authentication failed, handle accordingly
mySnackbarError('Authentication failed');
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addPassengerPrizeToWallet();
mySnackbarSuccess('Prize Added');
}
} catch (e) {
rethrow;
}
}
Future getPassengers() async {
var res = await CRUD().get(link: AppLink.getPassengerbyEmail, payload: {
'passengerEmail': passengerController.text,
'passengerId': passengerController.text,
'passengerphone': passengerController.text,
});
var d = jsonDecode(res);
if (d['status'] == 'success') {
passengers = d;
}
update();
}
changeHeight() {
if (passengers.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
void clearPlaces() {
passengers = {};
update();
}
@override
void onInit() {
getPassengerCount();
super.onInit();
}
}
@@ -0,0 +1,84 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class PromoController extends GetxController {
var promoList = [].obs;
var isLoading = false.obs;
final CRUD _crud = CRUD();
@override
void onInit() {
super.onInit();
getPromos();
}
Future<void> getPromos() async {
isLoading.value = true;
try {
var response = await _crud.get(link: AppLink.getPassengersPromo);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
promoList.assignAll(decoded['message']);
}
} else {
promoList.clear();
}
} catch (e) {
mySnackbarError("فشل جلب أكواد الخصم: $e");
} finally {
isLoading.value = false;
}
}
Future<bool> addPromo(Map<String, dynamic> data) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.addPassengersPromo, payload: data);
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
mySnackbarError("فشل إضافة كود الخصم: $e");
return false;
} finally {
isLoading.value = false;
}
}
Future<bool> deletePromo(String id) async {
try {
var response = await _crud.post(link: AppLink.deletePassengersPromo, payload: {"id": id});
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
mySnackbarError("فشل حذف كود الخصم: $e");
return false;
}
}
Future<bool> updatePromo(Map<String, dynamic> data) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.updatePassengersPromo, payload: data);
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
mySnackbarError("فشل تحديث كود الخصم: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,97 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class QualityController extends GetxController {
bool isLoading = false;
List driversBlacklist = [];
List passengersBlacklist = [];
Map scorecardData = {};
Future<void> fetchBlacklist() async {
isLoading = true;
update();
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {"action_type": "get_all"},
);
if (res is Map && res['status'] == 'success') {
driversBlacklist = res['message']['drivers'] ?? [];
passengersBlacklist = res['message']['passengers'] ?? [];
} else {
mySnackbarError("Failed to fetch blacklist");
}
} catch (e) {
mySnackbarError("Network error");
} finally {
isLoading = false;
update();
}
}
Future<void> unblockDriver(String phone) async {
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {
"action_type": "unblock_driver",
"phone": phone,
},
);
if (res is Map && res['status'] == 'success') {
mySnackbarSuccess("Driver unblocked successfully");
fetchBlacklist(); // Refresh
} else {
mySnackbarError(res['message'] ?? "Failed to unblock driver");
}
} catch (e) {
mySnackbarError("Network error");
}
}
Future<void> unblockPassenger(String phoneNormalized) async {
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {
"action_type": "unblock_passenger",
"phone_normalized": phoneNormalized,
},
);
if (res is Map && res['status'] == 'success') {
mySnackbarSuccess("Passenger unblocked successfully");
fetchBlacklist(); // Refresh
} else {
mySnackbarError(res['message'] ?? "Failed to unblock passenger");
}
} catch (e) {
mySnackbarError("Network error");
}
}
Future<void> fetchDriverScorecard(String driverId) async {
isLoading = true;
update();
try {
var res = await CRUD().post(
link: AppLink.driverScorecard,
payload: {"driver_id": driverId},
);
if (res is Map && res['status'] == 'success') {
scorecardData = res['message'];
} else {
mySnackbarError("Failed to fetch scorecard");
scorecardData = {};
}
} catch (e) {
mySnackbarError("Network error");
scorecardData = {};
} finally {
isLoading = false;
update();
}
}
}
@@ -0,0 +1,582 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class RegisterCaptainController extends GetxController {
bool isDriverSaved = false;
bool isCarSaved = false;
Map<String, dynamic>? arguments;
String? driverId;
String? email;
String? phone;
@override
void onInit() {
super.onInit();
arguments = Get.arguments;
initArguments();
}
void driveInit() {
arguments = Get.arguments;
initArguments();
}
void initArguments() {
if (arguments != null) {
driverId = arguments!['driverId'];
email = arguments!['email'];
phone = arguments!['phone_number'];
} else {
print('Arguments are null');
}
}
Map<String, dynamic> responseMap = {};
Map<String, dynamic> responseCarLicenseMapJordan = {};
Map<String, dynamic> responseBackCarLicenseMap = {};
Map<String, dynamic> responseIdCardMap = {};
Map<String, dynamic> responseIdCardDriverEgyptBack = {};
Map<String, dynamic> responseForComplaint = {};
Map<String, dynamic> responseIdCardDriverEgyptFront = {};
Map<String, dynamic> responseIdEgyptFront = {};
Map<String, dynamic> responseCriminalRecordEgypt = {};
Map<String, dynamic> responseIdEgyptBack = {};
Map<String, dynamic> responseIdEgyptDriverLicense = {};
String? responseIdCardDriverEgypt1;
bool isloading = false;
var image;
DateTime now = DateTime.now();
bool isLoading = false;
Future allMethodForAI(String prompt, imagePath, driverID) async {
isLoading = true;
update();
var extractedString = await CRUD().arabicTextExtractByVisionAndAI(
imagePath: imagePath, driverID: driverID);
var json = jsonDecode(extractedString);
var textValues = extractTextFromLines(json);
// await Get.put(AI()).geminiAiExtraction(prompt, textValues, imagePath);
await Get.put(RegisterCaptainController())
.anthropicAI(textValues, prompt, imagePath);
isLoading = false;
update();
}
String extractTextFromLines(Map<String, dynamic> jsonData) {
final readResult = jsonData['readResult'];
final blocks = readResult['blocks'];
final StringBuffer buffer = StringBuffer();
for (final block in blocks) {
final lines = block['lines'];
for (final line in lines) {
final text = line['text'];
buffer.write(text);
buffer.write('\n');
}
}
return buffer.toString().trim();
}
List driverNotCompleteRegistration = [];
getDriverNotCompleteRegistration() async {
var res = await CRUD()
.get(link: AppLink.getDriverNotCompleteRegistration, payload: {});
if (res != 'failure') {
var d = jsonDecode(res)['message'];
driverNotCompleteRegistration = d;
update();
} else {
mySnackbarError(res);
}
}
final today = DateTime.now();
Future<void> addDriverAndCarEgypt() async {
final expiryDate = responseIdEgyptDriverLicense['expiry_date'];
final expiryDateTime = DateTime.tryParse(expiryDate);
final isExpired = expiryDateTime != null && expiryDateTime.isBefore(today);
final taxExpiryDate = responseIdCardDriverEgyptBack['tax_expiry'];
// Get the inspection date from the response
final inspectionDate = responseIdCardDriverEgyptBack['inspection_date'];
final year = int.parse(inspectionDate.split('-')[0]);
// Try parsing the tax expiry date. If it fails, set it to null.
final taxExpiryDateTime = DateTime.tryParse(taxExpiryDate ?? '');
final isExpiredCar =
taxExpiryDateTime != null && taxExpiryDateTime.isBefore(today);
// Check if the inspection date is before today
final inspectionDateTime = DateTime(year, 1, 1);
final isInspectionExpired = inspectionDateTime.isBefore(today);
if (isExpiredCar || isInspectionExpired) {
Get.defaultDialog(
title: 'Expired Driver’s License'.tr,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'Your driver’s license and/or car tax has expired. Please renew them before proceeding.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'Your driver’s license and/or car tax has expired. Please renew them before proceeding.'
// .tr,
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
} else if (isExpired) {
Get.defaultDialog(
title: 'Expired Driver’s License'.tr,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'Your driver’s license has expired. Please renew it before proceeding.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'Your driver’s license has expired. Please renew it before proceeding.'
// .tr,
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
} else if (responseIdEgyptDriverLicense['national_number']
.toString()
.substring(0, 12) !=
responseIdEgyptBack['nationalID'].toString().substring(0, 12)) {
Get.defaultDialog(
barrierDismissible: false,
title: 'ID Mismatch',
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'The national number on your driver’s license does not match the one on your ID document. Please verify and provide the correct documents.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'The national number on your driver’s license does not match the one on your ID document. Please verify and provide the correct documents.',
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
}
// else if (responseCriminalRecordEgypt['FullName'] !=
// responseIdEgyptDriverLicense['name_arabic']) {
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Criminal Record Mismatch',
// content: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.warning, size: 48, color: Colors.red),
// const SizedBox(height: 16),
// Text(
// 'The full name on your criminal record does not match the one on your driver’s license. Please verify and provide the correct documents.'
// .tr,
// textAlign: TextAlign.center,
// style: AppStyle.title,
// ),
// const SizedBox(height: 16),
// IconButton(
// onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'The full name on your criminal record does not match the one on your driver’s license. Please verify and provide the correct documents.'
// .tr,
// );
// },
// icon: const Icon(Icons.volume_up),
// ),
// ],
// ),
// actions: [
// TextButton(
// onPressed: () {
// Get.back();
// },
// child: const Text('OK'),
// ),
// ],
// );
// }
else {
await addDriverEgypt();
await addRegistrationCarEgypt();
if (isCarSaved && isDriverSaved) {
mySnackbarSuccess('added'); // Get.offAll(() => HomeCaptain());
// Get.offAll(() => HomeCaptain());
}
}
}
Future<void> addDriverEgypt() async {
isLoading = true;
update();
var payload = {
'first_name': responseIdEgyptDriverLicense['firstName']?.toString() ??
'Not specified',
'last_name': responseIdEgyptDriverLicense['lastName']?.toString() ??
'Not specified',
'email': email?.toString() ?? 'Not specified',
'phone': phone?.toString() ?? 'Not specified',
'id': driverId?.toString() ?? 'Not specified',
'password': '123456',
'gender': responseIdEgyptBack['gender']?.toString() ?? 'Not specified',
'license_type':
responseIdEgyptDriverLicense['license_type']?.toString() ??
'Not specified',
'national_number':
responseIdEgyptBack['nationalID']?.toString() ?? 'Not specified',
'name_arabic': responseIdEgyptDriverLicense['name_arabic']?.toString() ??
'Not specified',
'name_english':
responseIdEgyptDriverLicense['name_english']?.toString() ??
'Not specified',
'issue_date': responseIdEgyptDriverLicense['issue_date']?.toString() ??
'Not specified',
'expiry_date': responseIdEgyptDriverLicense['expiry_date']?.toString() ??
'Not specified',
'license_categories': responseIdEgyptDriverLicense['license_categories']
is List
? responseIdEgyptDriverLicense['license_categories'].join(', ')
: responseIdEgyptDriverLicense['license_categories']?.toString() ??
'Not specified',
'address': responseIdEgyptFront['address']?.toString() ?? 'Not specified',
'card_id': responseIdEgyptFront['card_id']?.toString() ?? 'Not specified',
'occupation':
responseIdEgyptBack['occupation']?.toString() ?? 'Not specified',
'education':
responseIdEgyptBack['occupation']?.toString() ?? 'Not specified',
'licenseIssueDate':
responseIdEgyptDriverLicense['issue_date']?.toString() ??
'Not specified',
'religion':
responseIdEgyptBack['religion']?.toString() ?? 'Not specified',
'status': 'yet',
'birthdate': responseIdEgyptFront['dob']?.toString() ?? 'Not specified',
'maritalStatus':
responseIdEgyptBack['maritalStatus']?.toString() ?? 'Not specified',
'site': responseIdEgyptDriverLicense['address']?.toString() ??
'Not specified',
'employmentType':
responseIdEgyptDriverLicense['employmentType']?.toString() ??
'Not specified',
};
var res = await CRUD().post(link: AppLink.signUpCaptin, payload: payload);
var status1 = jsonDecode(res);
isLoading = false;
update();
// Handle response
if (status1['status'] == 'success') {
isDriverSaved = true;
mySnackbarSuccess('Driver data saved successfully');
} else {
mySnackbarError('Failed to save driver data');
}
}
addCriminalDeocuments() async {
var res = await CRUD().post(link: AppLink.addCriminalDocuments, payload: {
"driverId": box.read(BoxName.driverID),
"IssueDate": responseCriminalRecordEgypt['IssueDate'],
"InspectionResult": responseCriminalRecordEgypt['InspectionResult'],
});
if (res != 'failure') {
mySnackbarSuccess('uploaded sucssefuly'.tr);
}
}
Future addRegistrationCarEgypt() async {
try {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.addRegisrationCar, payload: {
'driverID': driverId,
'vin': responseIdCardDriverEgyptBack['chassis'].toString(),
'car_plate': responseIdCardDriverEgyptFront['car_plate'].toString(),
'make': responseIdCardDriverEgyptBack['make'].toString(),
'model': responseIdCardDriverEgyptBack['model'],
'year': responseIdCardDriverEgyptBack['year'].toString(),
'expiration_date':
responseIdCardDriverEgyptFront['LicenseExpirationDate'].toString(),
'color': responseIdCardDriverEgyptBack['color'],
'owner': responseIdCardDriverEgyptFront['owner'],
'color_hex': responseIdCardDriverEgyptBack['color_hex'].toString(),
'address': responseIdCardDriverEgyptFront['address'].toString(),
'displacement': responseIdCardDriverEgyptBack['engine'].toString(),
'fuel': responseIdCardDriverEgyptBack['fuel'].toString(),
'registration_date':
'${responseIdCardDriverEgyptBack['inspection_date']}',
});
isLoading = false;
update();
var status = jsonDecode(res);
if (status['status'] == 'success') {
isCarSaved = true;
mySnackbarSuccess('message');
}
} catch (e) {}
}
Future getComplaintDataToAI() async {
var res = await CRUD().get(
link: AppLink.getComplaintAllDataForDriver,
payload: {'driver_id': driverId.toString()},
);
if (res != 'failure') {
var d = jsonDecode(res)['message'];
return d;
} else {
return [
{'data': 'no data'}
];
}
}
Future<dynamic> anthropicAIForComplaint() async {
var dataComplaint = await getComplaintDataToAI();
var messagesData = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "$dataComplaint ${AppInformation.complaintPrompt} "
}
]
}
];
var requestBody = jsonEncode({
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"temperature": 0,
"system": "Json output only without any additional ",
"messages": messagesData,
});
final response = await http.post(
Uri.parse('https://api.anthropic.com/v1/messages'),
headers: {
'x-api-key': AK.anthropicAIkeySeferNew,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(utf8.decode(response.bodyBytes));
// Process the responseData as needed
responseForComplaint = jsonDecode(responseData['content'][0]['text']);
}
}
Future<dynamic> anthropicAI(
String payload, String prompt, String idType) async {
var messagesData = [
{
"role": "user",
"content": [
{"type": "text", "text": "$payload $prompt"}
]
}
];
var requestBody = jsonEncode({
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"temperature": 0,
"system": "Json output only without any additional ",
"messages": messagesData,
});
final response = await http.post(
Uri.parse('https://api.anthropic.com/v1/messages'),
headers: {
'x-api-key': AK.anthropicAIkeySeferNew,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(utf8.decode(response.bodyBytes));
// Process the responseData as needed
if (idType == 'car_back') {
responseIdCardDriverEgyptBack =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'car_front') {
responseIdCardDriverEgyptFront =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'id_front') {
responseIdEgyptFront = jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'id_back') {
responseIdEgyptBack = jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'driver_license') {
responseIdEgyptDriverLicense =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'criminalRecord') {
responseCriminalRecordEgypt =
jsonDecode(responseData['content'][0]['text']);
}
update();
return responseData.toString();
}
return responseIdCardDriverEgyptBack.toString();
}
Future<void> geminiAiExtraction(String prompt, payload, String idType) async {
var requestBody = jsonEncode({
"contents": [
{
"parts": [
{"text": "$payload $prompt"}
]
}
],
"generationConfig": {
"temperature": 1,
"topK": 64,
"topP": 0.95,
"maxOutputTokens": 8192,
"stopSequences": []
},
"safetySettings": [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
}
]
});
final response = await http.post(
Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=${AK.geminiApi}'),
'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro:generateContent?key=${AK.geminiApi}'),
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
// Process the responseData as needed
RegExp regex = RegExp(r"```json([^`]*)```");
String? jsonString =
regex.firstMatch(responseData.toString())?.group(1)?.trim();
if (jsonString != null) {
// Convert the JSON object to a String
jsonString = jsonEncode(json.decode(jsonString));
if (idType == 'car_back') {
responseIdCardDriverEgyptBack = jsonDecode(jsonString);
} else if (idType == 'car_front') {
responseIdCardDriverEgyptFront = jsonDecode(jsonString);
} else if (idType == 'id_front') {
responseIdEgyptFront = jsonDecode(jsonString);
} else if (idType == 'id_back') {
responseIdEgyptBack = jsonDecode(jsonString);
} else if (idType == 'driver_license') {
responseIdEgyptDriverLicense = jsonDecode(jsonString);
}
update();
} else {
mySnackbarError("JSON string not found");
}
// Rest of your code...
} else {}
}
}
@@ -0,0 +1,72 @@
import 'dart:convert';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../models/model/admin/monthly_ride.dart';
import '../functions/crud.dart';
class RideAdminController extends GetxController {
bool isLoading = false;
late List<MonthlyDataModel> rideData;
late Map<String, dynamic> jsonResponse;
List<dynamic> ridesDetails = [];
var chartData;
// late List<ChartDataS> chartDatasync;
Future getRidesAdminDash() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getRidesPerMonth, payload: {});
jsonResponse = jsonDecode(res);
rideData = (jsonResponse['message'] as List)
.map((item) => MonthlyDataModel.fromJson(item))
.toList();
chartData = rideData
.map((data) => FlSpot(data.day.toDouble(), data.ridesCount.toDouble()))
.toList();
// chartDatasync = (jsonResponse['message'] as List)
// .map((item) => ChartDataS(
// item['year'],
// item['month'],
// item['day'],
// item['rides_count'],
// ))
// .toList();
isLoading = false;
update();
}
Future getRidesDetails() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getRidesDetails, payload: {});
var d = jsonDecode(res);
ridesDetails = d['message'];
isLoading = false;
update();
}
@override
void onInit() async {
List<Future> initializationTasks = [
getRidesAdminDash(),
getRidesDetails(),
];
// cameras = await availableCameras();
await Future.wait(initializationTasks);
super.onInit();
}
}
// class ChartDataS {
// ChartDataS(this.year, this.month, this.day, this.ridesCount);
// final int year;
// final int month;
// final int day;
// final int ridesCount;
// }
@@ -0,0 +1,56 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.dart';
import '../../print.dart';
class SecurityV2Controller extends GetxController {
bool isLoading = true;
List<dynamic> auditLogs = [];
@override
void onInit() {
super.onInit();
fetchAuditLogs();
}
Future<void> fetchAuditLogs() async {
isLoading = true;
update();
try {
Log.print('Fetching from: ${AppLink.auditLogsV2}');
var res = await CRUD().get(link: AppLink.auditLogsV2, payload: {});
Log.print('Raw audit res type: ${res.runtimeType} | value: $res');
if (res == 'failure' || res == 'token_expired') {
Log.print('CRUD returned: $res');
mySnackbarError("السيرفر أرجع: $res");
auditLogs = [];
} else if (res != null) {
var d = res is String ? jsonDecode(res) : res;
Log.print('Decoded audit response: $d');
if (d['status'] == 'success') {
var message = d['message'];
if (message is List) {
auditLogs = message;
Log.print('Loaded ${auditLogs.length} audit logs');
} else {
auditLogs = [];
Log.print('message is not List: ${message.runtimeType}');
}
} else {
Log.print('Status not success: ${d['status']}');
mySnackbarError("${d['message'] ?? d['status']}");
}
}
} catch (e) {
Log.print('Error fetching audit logs: $e');
mySnackbarError("$e");
}
isLoading = false;
update();
}
}
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
import '../../views/widgets/snackbar.dart';
class StaffController extends GetxController {
final CRUD _crud = CRUD();
final formKey = GlobalKey<FormState>();
// التكست كنترولرز
final nameController = TextEditingController();
final phoneController = TextEditingController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final birthdateController = TextEditingController();
String selectedGender = 'Male';
String selectedRole = 'service'; // 'admin' or 'service'
String selectedCountry = 'Jordan';
bool isLoading = false;
Future<void> registerStaff() async {
if (!formKey.currentState!.validate()) return;
isLoading = true;
update();
try {
// ملاحظة: لا نأخذ بصمة جهاز الأدمن هنا، بل نتركها فارغة ليقوم الموظف بربطها عند أول دخول له
String fingerprint = "";
var response = await _crud.post(
link: AppLink.addStaff,
payload: {
"name": nameController.text.trim(),
"phone": phoneController.text.trim(),
"email": emailController.text.trim(),
"password": passwordController.text.trim(),
"role": selectedRole,
"gender": selectedGender,
"birthdate": birthdateController.text.trim(),
"fingerprint": fingerprint,
"site": "main", // القيمة الافتراضية للفرع
"country": selectedCountry,
},
);
if (response != "failure") {
mySnackbarSuccess('تمت إضافة الموظف بنجاح');
_clearFields();
Get.back();
} else {
mySnackbarError('فشل في إضافة الموظف. يرجى المحاولة لاحقاً');
}
} catch (e) {
mySnackbarError('حدث خطأ: $e');
} finally {
isLoading = false;
update();
}
}
void _clearFields() {
nameController.clear();
phoneController.clear();
emailController.clear();
passwordController.clear();
birthdateController.clear();
}
@override
void onClose() {
nameController.dispose();
phoneController.dispose();
emailController.dispose();
passwordController.dispose();
birthdateController.dispose();
super.onClose();
}
}
@@ -0,0 +1,514 @@
import 'dart:convert';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../constant/links.dart';
import '../../print.dart';
import '../functions/crud.dart';
// ══════════════════════════════════════════════════════════════
// MODEL: Represents one employee's full data for a period
// ══════════════════════════════════════════════════════════════
class EmployeeChartData {
final String name;
final Color color;
final List<FlSpot> notesSpots;
final List<FlSpot> callsSpots;
const EmployeeChartData({
required this.name,
required this.color,
required this.notesSpots,
required this.callsSpots,
});
int get totalNotes => notesSpots.fold(0, (sum, s) => sum + s.y.toInt());
int get totalCalls => callsSpots.fold(0, (sum, s) => sum + s.y.toInt());
EmployeeChartData copyWith({
List<FlSpot>? notesSpots,
List<FlSpot>? callsSpots,
}) {
return EmployeeChartData(
name: name,
color: color,
notesSpots: notesSpots ?? this.notesSpots,
callsSpots: callsSpots ?? this.callsSpots,
);
}
}
// ══════════════════════════════════════════════════════════════
// MODEL: Employment activation stats per employee
// ══════════════════════════════════════════════════════════════
class EmploymentStat {
final String name;
final int count;
final Color color;
const EmploymentStat({
required this.name,
required this.count,
required this.color,
});
}
// ══════════════════════════════════════════════════════════════
// CONTROLLER
// ══════════════════════════════════════════════════════════════
class StaticController extends GetxController {
// ─── Color Palette for Dynamic Employees ───────────────────
static const List<Color> _employeeColors = [
Color(0xFF00D4AA), // teal
Color(0xFF82AAFF), // blue
Color(0xFFFFCB6B), // amber
Color(0xFFC792EA), // purple
Color(0xFFFF5370), // red
Color(0xFFC3E88D), // green
Color(0xFFF07178), // coral
Color(0xFF89DDFF), // cyan
];
Color _colorForIndex(int i) => _employeeColors[i % _employeeColors.length];
// ─── Date & State ───────────────────────────────────────────
DateTime? startDate = DateTime(DateTime.now().year, DateTime.now().month, 1);
DateTime? endDate =
DateTime(DateTime.now().year, DateTime.now().month + 1, 0);
DateTime? compareStartDate;
DateTime? compareEndDate;
bool isComparing = false;
bool isLoading = false;
// ─── Daily Notes State ─────────────────────────────────────
bool isLoadingNotes = false;
List<dynamic> dailyNotesList = [];
// ─── Main Chart Data ───────────────────────────────────────
List<FlSpot> chartDataPassengers = [];
List<FlSpot> chartDataDrivers = [];
List<FlSpot> chartDataRides = [];
List<FlSpot> chartDataDriversMatchingNotes = [];
List<FlSpot> chartDataPassengersCompare = [];
List<FlSpot> chartDataDriversCompare = [];
List<FlSpot> chartDataRidesCompare = [];
List<FlSpot> chartDataDriversMatchingNotesCompare = [];
// ─── 🔥 DYNAMIC Employee Data ─────────────────────────────
// Key = employee name (from server), Value = their chart data
Map<String, EmployeeChartData> employeeData = {};
Map<String, EmployeeChartData> employeeDataCompare = {};
// Set of all known employee names (union of current + compare)
Set<String> get allEmployeeNames => {
...employeeData.keys,
...employeeDataCompare.keys,
};
// ─── Employment Stats ──────────────────────────────────────
List<EmploymentStat> employmentStatsList = [];
// ─── Totals ────────────────────────────────────────────────
String totalMonthlyPassengers = '0';
String totalMonthlyRides = '0';
String totalMonthlyDrivers = '0';
// ─── Raw Lists ─────────────────────────────────────────────
List staticList = [];
// ─── Color Registry (stable across rebuilds) ───────────────
final Map<String, Color> _employeeColorRegistry = {};
Color _getOrAssignColor(String name) {
if (!_employeeColorRegistry.containsKey(name)) {
_employeeColorRegistry[name] =
_colorForIndex(_employeeColorRegistry.length);
}
return _employeeColorRegistry[name]!;
}
@override
void onInit() {
super.onInit();
getAll();
}
// ─── Helpers ───────────────────────────────────────────────
double get daysInPeriod {
if (startDate == null || endDate == null) return 31;
return endDate!.difference(startDate!).inDays + 1.0;
}
String get currentDateString {
if (startDate == null || endDate == null) return "";
return "${DateFormat('yyyy-MM-dd').format(startDate!)} : "
"${DateFormat('yyyy-MM-dd').format(endDate!)}";
}
String get compareDateString {
if (compareStartDate == null || compareEndDate == null) return "";
return "${DateFormat('yyyy-MM-dd').format(compareStartDate!)} : "
"${DateFormat('yyyy-MM-dd').format(compareEndDate!)}";
}
// ─── Date Actions ──────────────────────────────────────────
void updateDateRange(DateTime start, DateTime end) {
startDate = start;
endDate = end;
if (isComparing) _calculateCompareDates();
getAll();
update();
}
void _calculateCompareDates() {
if (startDate == null || endDate == null) return;
Duration duration = endDate!.difference(startDate!);
compareEndDate = startDate!.subtract(const Duration(days: 1));
compareStartDate = compareEndDate!.subtract(duration);
}
Future<void> toggleComparison() async {
isComparing = !isComparing;
if (isComparing) {
_calculateCompareDates();
} else {
compareStartDate = null;
compareEndDate = null;
_clearComparisonData();
}
await getAll();
}
void _clearComparisonData() {
chartDataPassengersCompare.clear();
chartDataDriversCompare.clear();
chartDataRidesCompare.clear();
chartDataDriversMatchingNotesCompare.clear();
employeeDataCompare.clear();
}
Map<String, dynamic> _getPayload(DateTime start, DateTime end) => {
"start_date": DateFormat('yyyy-MM-dd').format(start),
"end_date": DateFormat('yyyy-MM-dd').format(end),
"month": start.month.toString(),
"year": start.year.toString(),
};
// ─── Main Fetch ────────────────────────────────────────────
Future<void> getAll() async {
if (startDate == null || endDate == null) return;
isLoading = true;
update();
await Future.wait([
fetchPassengers(isCompare: false),
fetchRides(isCompare: false),
fetchDrivers(isCompare: false),
fetchEmployeeDynamic(isCompare: false),
fetchEditorCallsDynamic(isCompare: false),
fetchEmploymentStats(),
]);
if (isComparing && compareStartDate != null && compareEndDate != null) {
await Future.wait([
fetchPassengers(isCompare: true),
fetchRides(isCompare: true),
fetchDrivers(isCompare: true),
fetchEmployeeDynamic(isCompare: true),
fetchEditorCallsDynamic(isCompare: true),
]);
}
isLoading = false;
update();
}
// ─── Spot Generator ───────────────────────────────────────
List<FlSpot> _generateSpots(
List<dynamic> data,
String dateKey,
String valueKey,
DateTime startOfRange,
DateTime endOfRange,
) {
Map<String, double> dataMap = {
for (var item in data)
item[dateKey].toString():
double.tryParse(item[valueKey].toString()) ?? 0.0
};
int totalDays = endOfRange.difference(startOfRange).inDays + 1;
return List.generate(totalDays, (i) {
final date = startOfRange.add(Duration(days: i));
final key = DateFormat('yyyy-MM-dd').format(date);
return FlSpot((i + 1).toDouble(), dataMap[key] ?? 0.0);
});
}
/// Generates spots map keyed by employee name from a date→name→value structure
Map<String, List<FlSpot>> _generateEmployeeSpots(
Map<String, Map<String, double>> dateNameMap,
DateTime start,
DateTime end,
) {
// Discover all employee names dynamically
final Set<String> names = {};
for (var dayData in dateNameMap.values) {
names.addAll(dayData.keys);
}
int totalDays = end.difference(start).inDays + 1;
final Map<String, List<FlSpot>> result = {};
for (final name in names) {
result[name] = List.generate(totalDays, (i) {
final date = start.add(Duration(days: i));
final dateStr = DateFormat('yyyy-MM-dd').format(date);
final value = dateNameMap[dateStr]?[name] ?? 0.0;
return FlSpot((i + 1).toDouble(), value);
});
}
return result;
}
/// Parses a list of {date/day, NAME, count} records into a dateNameMap
Map<String, Map<String, double>> _parseDateNameMap(List<dynamic> jsonData) {
final Map<String, Map<String, double>> result = {};
for (var item in jsonData) {
final dateStr = (item['date'] ?? item['day']).toString();
final name = item['NAME'].toString().toLowerCase().trim();
final count = double.tryParse(item['count'].toString()) ?? 0.0;
result.putIfAbsent(dateStr, () => {})[name] =
(result[dateStr]?[name] ?? 0) + count;
}
return result;
}
// ─── Passengers ───────────────────────────────────────────
Future<void> fetchPassengers({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getPassengersStatic, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
totalMonthlyPassengers = data[0]['totalMonthly'].toString();
}
final spots = _generateSpots(data, 'day', 'totalPassengers', start, end);
if (isCompare) {
chartDataPassengersCompare = spots;
} else {
chartDataPassengers = spots;
}
}
// ─── Rides ────────────────────────────────────────────────
Future<void> fetchRides({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD()
.get(link: AppLink.getRidesStatic, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
totalMonthlyRides = data[0]['totalMonthly'].toString();
}
final spots = _generateSpots(data, 'day', 'totalRides', start, end);
if (isCompare) {
chartDataRidesCompare = spots;
} else {
chartDataRides = spots;
}
}
// ─── Drivers ──────────────────────────────────────────────
Future<void> fetchDrivers({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getdriverstotalMonthly, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare &&
data.isNotEmpty &&
data[0]['totalMonthlyDrivers'] != null) {
totalMonthlyDrivers = data[0]['totalMonthlyDrivers'].toString();
staticList = data;
}
final spotsDrivers =
_generateSpots(data, 'day', 'dailyTotalDrivers', start, end);
final spotsNotes =
_generateSpots(data, 'day', 'dailyMatchingNotes', start, end);
if (isCompare) {
chartDataDriversCompare = spotsDrivers;
chartDataDriversMatchingNotesCompare = spotsNotes;
} else {
chartDataDrivers = spotsDrivers;
chartDataDriversMatchingNotes = spotsNotes;
}
}
// ─── 🔥 DYNAMIC: Employee Notes ───────────────────────────
Future<void> fetchEmployeeDynamic({bool isCompare = false}) async {
try {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEmployeeStatic, payload: _getPayload(start, end));
if (res == 'failure') return;
final json = jsonDecode(res) as Map<String, dynamic>;
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (data.isEmpty) return;
final dateNameMap = _parseDateNameMap(data);
final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);
// Merge into employee data map
final target = isCompare ? employeeDataCompare : employeeData;
spotsMap.forEach((name, spots) {
final color = _getOrAssignColor(name);
if (target.containsKey(name)) {
target[name] = target[name]!.copyWith(notesSpots: spots);
} else {
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: spots,
callsSpots: [],
);
}
});
} catch (e) {
Log.print('Error in fetchEmployeeDynamic: $e');
}
}
// ─── 🔥 DYNAMIC: Employee Calls ───────────────────────────
Future<void> fetchEditorCallsDynamic({bool isCompare = false}) async {
try {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEditorStatsCalls, payload: _getPayload(start, end));
if (res == 'failure') return;
final json = jsonDecode(res) as Map<String, dynamic>;
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (data.isEmpty) return;
final dateNameMap = _parseDateNameMap(data);
final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);
final target = isCompare ? employeeDataCompare : employeeData;
spotsMap.forEach((name, spots) {
final color = _getOrAssignColor(name);
if (target.containsKey(name)) {
target[name] = target[name]!.copyWith(callsSpots: spots);
} else {
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: [],
callsSpots: spots,
);
}
});
} catch (e) {
Log.print('Error in fetchEditorCallsDynamic: $e');
}
}
// ─── Employment Stats ─────────────────────────────────────
Future<void> fetchEmploymentStats() async {
try {
final res = await CRUD().get(
link: AppLink.getEmployeeDriverAfterCallingRegister,
payload: _getPayload(startDate!, endDate!));
if (res == 'failure') return;
final json = jsonDecode(res);
if (json['status'] != 'success') return;
final List<dynamic> data = json['message']?['data'] ?? [];
// Aggregate by name (dynamic — no hardcoded allowed list)
final Map<String, int> aggregated = {};
for (var item in data) {
final name = item['employmentType'].toString().toLowerCase().trim();
final count = int.tryParse(item['count'].toString()) ?? 0;
aggregated[name] = (aggregated[name] ?? 0) + count;
}
employmentStatsList = aggregated.entries.map((e) {
return EmploymentStat(
name: e.key,
count: e.value,
color: _getOrAssignColor(e.key),
);
}).toList()
..sort((a, b) => b.count.compareTo(a.count)); // sort descending
} catch (e) {
Log.print("Error fetchEmploymentStats: $e");
}
}
// ─── Daily Notes ──────────────────────────────────────────
Future<void> fetchDailyNotes(DateTime date) async {
try {
isLoadingNotes = true;
dailyNotesList.clear();
update();
final res = await CRUD().post(
link: AppLink.getNotesForEmployee,
payload: {"date": DateFormat('yyyy-MM-dd').format(date)});
if (res != 'failure') {
final json = res;
if (json['status'] == 'success') {
dailyNotesList = json['message'];
}
}
} catch (e) {
Log.print("Error fetchDailyNotes: $e");
} finally {
isLoadingNotes = false;
update();
}
}
// ─── Computed Summaries for UI ────────────────────────────
/// Returns sorted list of employees by total notes descending
List<EmployeeChartData> get employeesSortedByNotes {
final list = employeeData.values.toList();
list.sort((a, b) => b.totalNotes.compareTo(a.totalNotes));
return list;
}
/// Returns sorted list of employees by total calls descending
List<EmployeeChartData> get employeesSortedByCalls {
final list = employeeData.values.toList();
list.sort((a, b) => b.totalCalls.compareTo(a.totalCalls));
return list;
}
/// Grand total notes across all employees
int get grandTotalNotes =>
employeeData.values.fold(0, (s, e) => s + e.totalNotes);
/// Grand total calls across all employees
int get grandTotalCalls =>
employeeData.values.fold(0, (s, e) => s + e.totalCalls);
}
@@ -0,0 +1,195 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../functions/crud.dart';
class WalletAdminController extends GetxController {
bool isLoading = false;
late Map<String, dynamic> jsonResponse;
List<dynamic> walletDetails = [];
List driversWalletPoints = [];
@override
void onInit() {
getWalletForEachDriverToPay();
super.onInit();
}
Future getWalletAdminDash() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getRidesPerMonth, payload: {});
jsonResponse = jsonDecode(res);
}
Future payToBankDriverAll() async {
for (var i = 0; i < driversWalletPoints.length; i++) {
String token = await getToken();
await Future.delayed(const Duration(seconds: 1));
try {
await payToDriverBankAccount(
token,
driversWalletPoints[i]['total_amount'].toString(),
driversWalletPoints[i]['accountBank'].toString(),
driversWalletPoints[i]['bankCode'].toString(),
driversWalletPoints[i]['name_arabic'].toString(),
driversWalletPoints[i]['driverID'].toString(),
driversWalletPoints[i]['phone'].toString(),
driversWalletPoints[i]['email'].toString(),
);
await Future.delayed(const Duration(seconds: 3));
} on FormatException {
// Handle the error or rethrow the exception as needed
}
}
}
Future<void> payToDriverBankAccount(
String token,
String amount,
String bankCardNumber,
String bankCode,
String name,
String driverId,
String phone,
String email) async {
var headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
var body = jsonEncode({
"issuer": "bank_card",
"amount": amount,
"full_name": name,
"bank_card_number": bankCardNumber,
"bank_code": bankCode,
"bank_transaction_type": "cash_transfer"
});
var response = await http.post(
Uri.parse(
'https://stagingpayouts.paymobsolutions.com/api/secure/disburse/'),
headers: headers,
body: body);
if (response.statusCode == 200) {
var d = jsonDecode(response.body);
if (d['status_description'] ==
"Transaction received and validated successfully. Dispatched for being processed by the bank") {
await addPayment('payFromSeferToDriver', driverId,
((-1) * double.parse(amount)).toString());
await addSeferWallet('payFromSeferToDriver', driverId,
((-1) * double.parse(amount)).toString());
await updatePaymentToPaid(driverId);
await sendEmail(driverId, amount, phone, name, bankCardNumber, email);
}
} else {}
}
// String paymentToken = '';
Future<String> generateToken(String amount) async {
var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'amount': amount.toString(),
});
var d = jsonDecode(res);
return d['message'];
}
Future sendEmail(
String driverId, amount, phone, name, bankCardNumber, email) async {
await CRUD().sendEmail(AppLink.sendEmailToDrivertransaction, {
"driverID": driverId,
"total_amount": amount,
"phone": phone,
"name_arabic": name,
"accountBank": bankCardNumber,
"email": email
});
}
Future addSeferWallet(
String paymentMethod, String driverID, String point) async {
var seferToken = await generateToken(point.toString());
await CRUD().post(link: AppLink.addSeferWallet, payload: {
'amount': point.toString(),
'paymentMethod': paymentMethod,
'passengerId': 'driver',
'token': seferToken,
'driverId': driverID.toString(),
});
}
Future addPayment(
String paymentMethod, String driverID, String amount) async {
var paymentToken =
await generateToken(((double.parse(amount))).toStringAsFixed(0));
await CRUD().post(link: AppLink.addDrivePayment, payload: {
'rideId': DateTime.now().toIso8601String(),
'amount': ((double.parse(amount))).toStringAsFixed(0),
'payment_method': paymentMethod,
'passengerID': 'myself',
'token': paymentToken,
'driverID': driverID.toString(),
});
}
Future updatePaymentToPaid(String driverID) async {
await CRUD().post(link: AppLink.updatePaymetToPaid, payload: {
'driverID': driverID.toString(),
});
}
Future<String> getToken() async {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
// 'Cookie':
// 'csrftoken=74iZJ8XYyuTm5WRq2W4tpWX5eqoJLZVK5QhuDrChWpDtzpgGA269bbCWuEcW85t4'
};
var body = {
'grant_type': 'password',
'username': AK.payMobOutUserName,
'password': AK.payMobOutPassword,
'client_id': AK.payMobOutClient_id,
'client_secret': AK.payMobOutClientSecrret
};
var res = await http.post(
Uri.parse(
'https://stagingpayouts.paymobsolutions.com/api/secure/o/token/'),
headers: headers,
body: body,
);
String token = '';
if (res.statusCode == 200) {
var decode = jsonDecode(res.body);
token = decode['access_token'];
}
return token;
}
Future getWalletForEachDriverToPay() async {
isLoading = true;
update();
var res = await CRUD()
.postWallet(link: AppLink.getVisaForEachDriver, payload: {});
var d = (res);
if (d != 'failure') {
driversWalletPoints = d['message'] ?? [];
isLoading = false;
update();
} else {
driversWalletPoints = [];
isLoading = false;
update();
}
}
}