2026-03-10-1

This commit is contained in:
Hamza-Ayed
2026-03-10 00:02:17 +03:00
parent c000d22ca3
commit cdda136006
28 changed files with 9912 additions and 3592 deletions

View File

@@ -53,7 +53,7 @@ class CaptainAdminController extends GetxController {
update();
var res = await CRUD().post(
link: AppLink.find_driver_by_phone,
payload: {'phone': phone},
payload: {'phone': "963$phone"},
);
var d = (res);
if (d != 'failure') {

View File

@@ -54,14 +54,14 @@ class DashboardController extends GetxController {
}
// 🔹 Check SMS credit
var res2 = await CRUD().kazumiSMS(
link: 'https://sms.kazumi.me/api/sms/check-credit',
payload: {"username": "Sefer", "password": AK.smsPasswordEgypt},
);
// 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');
// creditSMS = res2['credit'];
// print('📱 SMS Credit Response: ${jsonEncode(res2)}');
// print('💰 creditSMS: $creditSMS');
isLoading = false;
update();

View File

@@ -1,5 +1,6 @@
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';
@@ -7,8 +8,72 @@ 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 {
// --- Date & State Management ---
// ─── 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);
@@ -19,56 +84,53 @@ class StaticController extends GetxController {
bool isComparing = false;
bool isLoading = false;
// --- Daily Notes State ---
// ─── Daily Notes State ─────────────────────────────────────
bool isLoadingNotes = false;
List<dynamic> dailyNotesList = [];
// --- Chart Data (Current Range) ---
// ─── Main Chart Data ───────────────────────────────────────
List<FlSpot> chartDataPassengers = [];
List<FlSpot> chartDataDrivers = [];
List<FlSpot> chartDataRides = [];
List<FlSpot> chartDataDriversMatchingNotes = [];
// Employee Data (Notes/General Stats)
List<FlSpot> chartDataEmployeerama1 = [];
List<FlSpot> chartDataEmployeeshahd = [];
List<FlSpot> chartDataEmployeeRama2 = [];
List<FlSpot> chartDataEmployeeSefer4 = [];
// Employee Data (Calls/Activations Stats)
List<FlSpot> chartDataCallsrama1 = [];
List<FlSpot> chartDataCallsShahd = [];
List<FlSpot> chartDataCallsRama2 = [];
List<FlSpot> chartDataCallsSefer4 = [];
// --- Chart Data (Comparison Range) ---
List<FlSpot> chartDataPassengersCompare = [];
List<FlSpot> chartDataDriversCompare = [];
List<FlSpot> chartDataRidesCompare = [];
List<FlSpot> chartDataDriversMatchingNotesCompare = [];
// Employee Comparison (Notes)
List<FlSpot> chartDataEmployeerama1Compare = [];
List<FlSpot> chartDataEmployeeshahdCompare = [];
List<FlSpot> chartDataEmployeeRama2Compare = [];
List<FlSpot> chartDataEmployeeSefer4Compare = [];
// ─── 🔥 DYNAMIC Employee Data ─────────────────────────────
// Key = employee name (from server), Value = their chart data
Map<String, EmployeeChartData> employeeData = {};
Map<String, EmployeeChartData> employeeDataCompare = {};
// Employee Comparison (Calls/Activations)
List<FlSpot> chartDataCallsrama1Compare = [];
List<FlSpot> chartDataCallsShahdCompare = [];
List<FlSpot> chartDataCallsRama2Compare = [];
List<FlSpot> chartDataCallsSefer4Compare = [];
// Set of all known employee names (union of current + compare)
Set<String> get allEmployeeNames => {
...employeeData.keys,
...employeeDataCompare.keys,
};
// --- Totals ---
// ─── Employment Stats ──────────────────────────────────────
List<EmploymentStat> employmentStatsList = [];
// ─── Totals ────────────────────────────────────────────────
String totalMonthlyPassengers = '0';
String totalMonthlyRides = '0';
String totalMonthlyDrivers = '0';
// --- Raw Lists ---
// ─── Raw Lists ─────────────────────────────────────────────
List staticList = [];
// --- Employment Type Stats List (Simple Count) ---
List<Map<String, dynamic>> employmentStatsList = [];
// ─── 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() {
@@ -76,7 +138,7 @@ class StaticController extends GetxController {
getAll();
}
// --- Helpers for View ---
// ─── Helpers ───────────────────────────────────────────────
double get daysInPeriod {
if (startDate == null || endDate == null) return 31;
return endDate!.difference(startDate!).inDays + 1.0;
@@ -84,10 +146,17 @@ class StaticController extends GetxController {
String get currentDateString {
if (startDate == null || endDate == null) return "";
return "${DateFormat('yyyy-MM-dd').format(startDate!)} : ${DateFormat('yyyy-MM-dd').format(endDate!)}";
return "${DateFormat('yyyy-MM-dd').format(startDate!)} : "
"${DateFormat('yyyy-MM-dd').format(endDate!)}";
}
// --- Date Actions ---
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;
@@ -120,29 +189,18 @@ class StaticController extends GetxController {
chartDataDriversCompare.clear();
chartDataRidesCompare.clear();
chartDataDriversMatchingNotesCompare.clear();
chartDataEmployeerama1Compare.clear();
chartDataEmployeeshahdCompare.clear();
chartDataEmployeeRama2Compare.clear();
chartDataEmployeeSefer4Compare.clear();
chartDataCallsrama1Compare.clear();
chartDataCallsShahdCompare.clear();
chartDataCallsRama2Compare.clear();
chartDataCallsSefer4Compare.clear();
employeeDataCompare.clear();
}
Map<String, dynamic> _getPayload(DateTime start, DateTime end) {
return {
"start_date": DateFormat('yyyy-MM-dd').format(start),
"end_date": DateFormat('yyyy-MM-dd').format(end),
"month": start.month.toString(),
"year": start.year.toString(),
};
}
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 Logic ---
Future getAll() async {
// ─── Main Fetch ────────────────────────────────────────────
Future<void> getAll() async {
if (startDate == null || endDate == null) return;
isLoading = true;
@@ -152,8 +210,8 @@ class StaticController extends GetxController {
fetchPassengers(isCompare: false),
fetchRides(isCompare: false),
fetchDrivers(isCompare: false),
fetchEmployee(isCompare: false),
fetchEditorCalls(isCompare: false),
fetchEmployeeDynamic(isCompare: false),
fetchEditorCallsDynamic(isCompare: false),
fetchEmploymentStats(),
]);
@@ -162,8 +220,8 @@ class StaticController extends GetxController {
fetchPassengers(isCompare: true),
fetchRides(isCompare: true),
fetchDrivers(isCompare: true),
fetchEmployee(isCompare: true),
fetchEditorCalls(isCompare: true),
fetchEmployeeDynamic(isCompare: true),
fetchEditorCallsDynamic(isCompare: true),
]);
}
@@ -171,90 +229,124 @@ class StaticController extends GetxController {
update();
}
// ... (Existing Functions _generateSpots, fetchPassengers, etc.) ...
List<FlSpot> _generateSpots(List<dynamic> data, String dateKey,
String valueKey, DateTime startOfRange) {
List<FlSpot> spots = [];
Map<String, double> dataMap = {};
for (var item in data) {
String dateStr = item[dateKey].toString();
double val = double.tryParse(item[valueKey].toString()) ?? 0.0;
dataMap[dateStr] = val;
}
DateTime rangeEnd =
(startOfRange == startDate) ? endDate! : compareEndDate!;
int totalDays = rangeEnd.difference(startOfRange).inDays + 1;
for (int i = 0; i < totalDays; i++) {
DateTime currentDate = startOfRange.add(Duration(days: i));
String dateKeyStr = DateFormat('yyyy-MM-dd').format(currentDate);
double value = dataMap[dateKeyStr] ?? 0.0;
spots.add(FlSpot((i + 1).toDouble(), value));
}
return spots;
// ─── 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);
});
}
Future<void> fetchPassengers({bool isCompare = false}) async {
DateTime start = isCompare ? compareStartDate! : startDate!;
DateTime end = isCompare ? compareEndDate! : endDate!;
var res = await CRUD().get(
link: AppLink.getPassengersStatic, payload: _getPayload(start, end));
var jsonResponse = jsonDecode(res);
if (jsonResponse['status'] == 'failure') return;
final List<dynamic> jsonData = jsonResponse['message'];
if (!isCompare &&
jsonData.isNotEmpty &&
jsonData[0]['totalMonthly'] != null) {
totalMonthlyPassengers = jsonData[0]['totalMonthly'].toString();
/// 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);
}
List<FlSpot> spots =
_generateSpots(jsonData, 'day', 'totalPassengers', start);
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 {
DateTime start = isCompare ? compareStartDate! : startDate!;
DateTime end = isCompare ? compareEndDate! : endDate!;
var res = await CRUD()
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD()
.get(link: AppLink.getRidesStatic, payload: _getPayload(start, end));
var jsonResponse = jsonDecode(res);
if (jsonResponse['status'] == 'failure') return;
final List<dynamic> jsonData = jsonResponse['message'];
if (!isCompare &&
jsonData.isNotEmpty &&
jsonData[0]['totalMonthly'] != null) {
totalMonthlyRides = jsonData[0]['totalMonthly'].toString();
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();
}
List<FlSpot> spots = _generateSpots(jsonData, 'day', 'totalRides', start);
final spots = _generateSpots(data, 'day', 'totalRides', start, end);
if (isCompare)
chartDataRidesCompare = spots;
else
chartDataRides = spots;
}
// ─── Drivers ──────────────────────────────────────────────
Future<void> fetchDrivers({bool isCompare = false}) async {
DateTime start = isCompare ? compareStartDate! : startDate!;
DateTime end = isCompare ? compareEndDate! : endDate!;
var res = await CRUD().get(
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getdriverstotalMonthly, payload: _getPayload(start, end));
var jsonResponse = jsonDecode(res);
if (jsonResponse['status'] == 'failure') return;
final List<dynamic> jsonData = jsonResponse['message'];
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare &&
jsonData.isNotEmpty &&
jsonData[0]['totalMonthlyDrivers'] != null) {
totalMonthlyDrivers = jsonData[0]['totalMonthlyDrivers'].toString();
data.isNotEmpty &&
data[0]['totalMonthlyDrivers'] != null) {
totalMonthlyDrivers = data[0]['totalMonthlyDrivers'].toString();
staticList = data;
}
if (!isCompare) {
staticList = jsonData;
}
List<FlSpot> spotsDrivers =
_generateSpots(jsonData, 'day', 'dailyTotalDrivers', start);
List<FlSpot> spotsNotes =
_generateSpots(jsonData, 'day', 'dailyMatchingNotes', start);
final spotsDrivers =
_generateSpots(data, 'day', 'dailyTotalDrivers', start, end);
final spotsNotes =
_generateSpots(data, 'day', 'dailyMatchingNotes', start, end);
if (isCompare) {
chartDataDriversCompare = spotsDrivers;
chartDataDriversMatchingNotesCompare = spotsNotes;
@@ -264,237 +356,126 @@ class StaticController extends GetxController {
}
}
Future<void> fetchEmployee({bool isCompare = false}) async {
// ─── 🔥 DYNAMIC: Employee Notes ───────────────────────────
Future<void> fetchEmployeeDynamic({bool isCompare = false}) async {
try {
DateTime start = isCompare ? compareStartDate! : startDate!;
DateTime end = isCompare ? compareEndDate! : endDate!;
var res = await CRUD().get(
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEmployeeStatic, payload: _getPayload(start, end));
if (isCompare) {
chartDataEmployeerama1Compare = [];
chartDataEmployeeshahdCompare = [];
chartDataEmployeeRama2Compare = [];
chartDataEmployeeSefer4Compare = [];
} else {
chartDataEmployeerama1 = [];
chartDataEmployeeshahd = [];
chartDataEmployeeRama2 = [];
chartDataEmployeeSefer4 = [];
}
if (res == 'failure') return;
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
if (jsonResponse['status'] == 'failure') return;
final List<dynamic> jsonData = jsonResponse['message'];
if (jsonData.isEmpty) return;
final json = jsonDecode(res) as Map<String, dynamic>;
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (data.isEmpty) return;
Map<String, Map<String, double>> dateNameMap = {};
for (var item in jsonData) {
String dateKeyStr = item['date'] ?? item['day'];
String name = item['NAME'].toString().toLowerCase().trim();
double count = double.tryParse(item['count'].toString()) ?? 0.0;
if (!dateNameMap.containsKey(dateKeyStr)) dateNameMap[dateKeyStr] = {};
if (dateNameMap[dateKeyStr]!.containsKey(name)) {
dateNameMap[dateKeyStr]![name] =
dateNameMap[dateKeyStr]![name]! + count;
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 {
dateNameMap[dateKeyStr]![name] = count;
}
}
final targetLists = isCompare
? {
'rama1': chartDataEmployeerama1Compare,
'shahd': chartDataEmployeeshahdCompare,
'rama2': chartDataEmployeeRama2Compare,
'mayar': chartDataEmployeeSefer4Compare
}
: {
'rama1': chartDataEmployeerama1,
'shahd': chartDataEmployeeshahd,
'rama2': chartDataEmployeeRama2,
'mayar': chartDataEmployeeSefer4
};
int totalDays = end.difference(start).inDays + 1;
targetLists.forEach((key, listToFill) {
for (int i = 0; i < totalDays; i++) {
DateTime currentDate = start.add(Duration(days: i));
String currentDateStr = DateFormat('yyyy-MM-dd').format(currentDate);
double value = 0;
Map<String, double>? dayData = dateNameMap[currentDateStr];
if (dayData != null) {
// if (key == 'mayar') {
// value = (dayData['mayar'] ?? 0) +
// (dayData['rama1'] ?? 0) +
// (dayData['sefer4'] ?? 0);
// } else {
value = dayData[key] ?? 0;
// }
}
listToFill.add(FlSpot((i + 1).toDouble(), value));
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: spots,
callsSpots: [],
);
}
});
} catch (e) {
Log.print('Error in fetchEmployee: $e');
Log.print('Error in fetchEmployeeDynamic: $e');
}
}
Future<void> fetchEditorCalls({bool isCompare = false}) async {
// ─── 🔥 DYNAMIC: Employee Calls ───────────────────────────
Future<void> fetchEditorCallsDynamic({bool isCompare = false}) async {
try {
DateTime start = isCompare ? compareStartDate! : startDate!;
DateTime end = isCompare ? compareEndDate! : endDate!;
var res = await CRUD().get(
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEditorStatsCalls, payload: _getPayload(start, end));
if (isCompare) {
chartDataCallsrama1Compare = [];
chartDataCallsShahdCompare = [];
chartDataCallsRama2Compare = [];
chartDataCallsSefer4Compare = [];
} else {
chartDataCallsrama1 = [];
chartDataCallsShahd = [];
chartDataCallsRama2 = [];
chartDataCallsSefer4 = [];
}
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;
var jsonResponse = jsonDecode(res) as Map<String, dynamic>;
if (jsonResponse['status'] == 'failure') return;
final dateNameMap = _parseDateNameMap(data);
final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);
final List<dynamic> jsonData = jsonResponse['message'];
if (jsonData.isEmpty) return;
final target = isCompare ? employeeDataCompare : employeeData;
Map<String, Map<String, double>> dateNameMap = {};
for (var item in jsonData) {
String dateKeyStr = item['date'] ?? item['day'];
String name = item['NAME'].toString().toLowerCase().trim();
double count = double.tryParse(item['count'].toString()) ?? 0.0;
if (!dateNameMap.containsKey(dateKeyStr)) {
dateNameMap[dateKeyStr] = {};
}
if (dateNameMap[dateKeyStr]!.containsKey(name)) {
dateNameMap[dateKeyStr]![name] =
dateNameMap[dateKeyStr]![name]! + count;
spotsMap.forEach((name, spots) {
final color = _getOrAssignColor(name);
if (target.containsKey(name)) {
target[name] = target[name]!.copyWith(callsSpots: spots);
} else {
dateNameMap[dateKeyStr]![name] = count;
}
}
final targetLists = isCompare
? {
'rama1': chartDataCallsrama1Compare,
'shahd': chartDataCallsShahdCompare,
'rama2': chartDataCallsRama2Compare,
'mayar': chartDataCallsSefer4Compare,
}
: {
'rama1': chartDataCallsrama1,
'shahd': chartDataCallsShahd,
'rama2': chartDataCallsRama2,
'mayar': chartDataCallsSefer4,
};
int totalDays = end.difference(start).inDays + 1;
targetLists.forEach((key, listToFill) {
for (int i = 0; i < totalDays; i++) {
DateTime currentDate = start.add(Duration(days: i));
String currentDateStr = DateFormat('yyyy-MM-dd').format(currentDate);
double value = 0;
Map<String, double>? dayData = dateNameMap[currentDateStr];
if (dayData != null) {
// if (key == 'mayar_group') {
// value = (dayData['mayar'] ?? 0) +
// (dayData['rama1'] ?? 0) +
// (dayData['sefer4'] ?? 0);
// } else {
value = dayData[key] ?? 0;
// }
}
listToFill.add(FlSpot((i + 1).toDouble(), value));
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: [],
callsSpots: spots,
);
}
});
} catch (e) {
Log.print('Error in fetchEditorCalls: $e');
Log.print('Error in fetchEditorCallsDynamic: $e');
}
}
// --- 🔴 FIXED: Fetch Employment Stats with Unique Check ---
// ─── Employment Stats ─────────────────────────────────────
Future<void> fetchEmploymentStats() async {
try {
// لا نستخدم .clear() هنا، سنقوم باستبدال القائمة بالكامل في النهاية
var res = await CRUD().get(
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'] ?? [];
var jsonResponse = jsonDecode(res);
if (jsonResponse['status'] == 'success') {
if (jsonResponse['message'] != null &&
jsonResponse['message']['data'] != null) {
List<dynamic> data = jsonResponse['message']['data'];
List<String> allowedNames = ['shahd', 'mayar', 'rama1', 'rama2'];
// استخدام Map لضمان عدم تكرار الأسماء (تجميع القيم)
Map<String, int> uniqueMap = {};
for (var item in data) {
String name =
item['employmentType'].toString().toLowerCase().trim();
int count = int.tryParse(item['count'].toString()) ?? 0;
if (allowedNames.contains(name)) {
if (uniqueMap.containsKey(name)) {
uniqueMap[name] = uniqueMap[name]! + count;
} else {
uniqueMap[name] = count;
}
}
}
// تحويل الـ Map إلى القائمة النهائية
List<Map<String, dynamic>> tempList = [];
uniqueMap.forEach((key, value) {
tempList.add({'name': key, 'count': value});
});
// استبدال القائمة القديمة بالقائمة الجديدة النظيفة
employmentStatsList = tempList;
}
// 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");
}
}
// --- Fetch Daily Notes Log ---
// ─── Daily Notes ──────────────────────────────────────────
Future<void> fetchDailyNotes(DateTime date) async {
try {
isLoadingNotes = true;
dailyNotesList.clear();
update();
var res = await CRUD().post(
final res = await CRUD().post(
link: AppLink.getNotesForEmployee,
payload: {"date": DateFormat('yyyy-MM-dd').format(date)});
if (res != 'failure') {
var jsonResponse = (res);
if (jsonResponse['status'] == 'success') {
dailyNotesList = jsonResponse['message'];
final json = res;
if (json['status'] == 'success') {
dailyNotesList = json['message'];
}
}
} catch (e) {
@@ -504,4 +485,28 @@ class StaticController extends GetxController {
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);
}