first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,323 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/analytics_v2_controller.dart';
import 'package:intl/intl.dart';
class AdvancedAnalyticsPage extends StatelessWidget {
const AdvancedAnalyticsPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(AnalyticsV2Controller());
return Scaffold(
backgroundColor: AppColor.bg,
appBar: AppBar(
title: const Text('التحليلات المتقدمة',
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: AppColor.surface,
elevation: 0,
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () => controller.fetchAllAnalytics(),
)
],
),
body: GetBuilder<AnalyticsV2Controller>(
builder: (ctrl) {
if (ctrl.isLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColor.accent));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSummarySection(ctrl.revenueData['summary']),
const SizedBox(height: 24),
_buildSectionTitle('إيرادات آخر 30 يوم'),
_buildRevenueChart(ctrl.revenueData['daily'] ?? []),
const SizedBox(height: 32),
_buildSectionTitle('نمو المستخدمين (آخر 30 يوم)'),
_buildGrowthChart(ctrl.growthData),
const SizedBox(height: 32),
_buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)'),
_buildTopDriversList(ctrl.topDrivers),
const SizedBox(height: 40),
],
),
);
},
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
title,
style: const TextStyle(
color: AppColor.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildSummarySection(Map<String, dynamic>? summary) {
if (summary == null) return const SizedBox();
return Row(
children: [
_buildSummaryCard('إجمالي الإيرادات',
'${summary['total_revenue_all'] ?? 0}', AppColor.info),
const SizedBox(width: 12),
_buildSummaryCard('صافي الربح', '${summary['total_profit_all'] ?? 0}',
AppColor.success),
],
);
}
Widget _buildSummaryCard(String title, String value, Color color) {
return Expanded(
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
const SizedBox(height: 8),
Text(value,
style: TextStyle(
color: color, fontSize: 22, fontWeight: FontWeight.bold)),
],
),
),
);
}
Widget _buildRevenueChart(List<dynamic> daily) {
if (daily.isEmpty) return const Center(child: Text('لا توجد بيانات'));
List<FlSpot> revenueSpots = [];
List<FlSpot> profitSpots = [];
for (int i = 0; i < daily.length; i++) {
revenueSpots.add(FlSpot(i.toDouble(),
double.tryParse(daily[i]['total_revenue'].toString()) ?? 0));
profitSpots.add(FlSpot(i.toDouble(),
double.tryParse(daily[i]['company_profit'].toString()) ?? 0));
}
return Container(
height: 300,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(24),
),
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (v) =>
FlLine(color: AppColor.divider, strokeWidth: 1)),
titlesData: FlTitlesData(
show: true,
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 && val.toInt() < daily.length) {
return Text(
daily[val.toInt()]['date'].toString().substring(8),
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 10));
}
return const SizedBox();
},
),
),
),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: revenueSpots,
isCurved: true,
color: AppColor.info,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true, color: AppColor.info.withOpacity(0.1)),
),
LineChartBarData(
spots: profitSpots,
isCurved: true,
color: AppColor.success,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true, color: AppColor.success.withOpacity(0.1)),
),
],
),
),
);
}
Widget _buildGrowthChart(Map<String, dynamic> data) {
final passengers = data['passenger_daily'] as List<dynamic>? ?? [];
final drivers = data['driver_daily'] as List<dynamic>? ?? [];
if (passengers.isEmpty && drivers.isEmpty)
return const Center(child: Text('لا توجد بيانات'));
List<BarChartGroupData> barGroups = [];
int maxLength =
passengers.length > drivers.length ? passengers.length : drivers.length;
for (int i = 0; i < maxLength; i++) {
double pCount = i < passengers.length
? double.tryParse(passengers[i]['new_passengers'].toString()) ?? 0
: 0;
double dCount = i < drivers.length
? double.tryParse(drivers[i]['new_drivers'].toString()) ?? 0
: 0;
barGroups.add(
BarChartGroupData(
x: i,
barRods: [
BarChartRodData(
toY: pCount,
color: AppColor.info,
width: 8,
borderRadius: BorderRadius.circular(4)),
BarChartRodData(
toY: dCount,
color: AppColor.warning,
width: 8,
borderRadius: BorderRadius.circular(4)),
],
),
);
}
return Container(
height: 250,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(24),
),
child: BarChart(
BarChartData(
barGroups: barGroups,
borderData: FlBorderData(show: false),
titlesData: FlTitlesData(
show: true,
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 && val.toInt() < passengers.length) {
return Text(
passengers[val.toInt()]['date'].toString().substring(8),
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 10));
}
return const SizedBox();
},
),
),
),
),
),
);
}
Widget _buildTopDriversList(List<dynamic> drivers) {
if (drivers.isEmpty) return const Center(child: Text('لا توجد بيانات'));
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: drivers.length,
itemBuilder: (ctx, i) {
final d = drivers[i];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
CircleAvatar(
backgroundColor: AppColor.accent.withOpacity(0.1),
child: Text('${i + 1}',
style: const TextStyle(
color: AppColor.accent, fontWeight: FontWeight.bold)),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${d['first_name']} ${d['last_name']}',
style: const TextStyle(
color: AppColor.textPrimary,
fontWeight: FontWeight.bold)),
Text(d['phone'] ?? '',
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('${d['completed_rides']} رحلة',
style: const TextStyle(
color: AppColor.info, fontWeight: FontWeight.bold)),
Text('${d['total_revenue']} ل.س',
style: const TextStyle(
color: AppColor.success,
fontSize: 12,
fontWeight: FontWeight.bold)),
],
),
],
),
);
},
);
}
}

View File

@@ -0,0 +1,163 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart' hide TextDirection;
import 'package:siro_admin/controller/functions/launch.dart';
import '../../../controller/admin/static_controller.dart';
import '../../widgets/mycircular.dart';
class DailyNotesView extends StatelessWidget {
const DailyNotesView({super.key});
@override
Widget build(BuildContext context) {
// نستخدم نفس الكونترولر للوصول لدالة جلب الملاحظات
final controller = Get.find<StaticController>();
// عند فتح الصفحة، نجلب ملاحظات اليوم الحالي
WidgetsBinding.instance.addPostFrameCallback((_) {
controller.fetchDailyNotes(DateTime.now());
});
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
title: Text(
'سجل المكالمات اليومي',
style: const TextStyle(
color: Color(0xFF1A1A1A),
fontWeight: FontWeight.w800,
fontSize: 20,
),
),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0,
iconTheme: const IconThemeData(color: Colors.black87),
),
body: GetBuilder<StaticController>(
builder: (controller) {
if (controller.isLoadingNotes) {
return const Center(child: MyCircularProgressIndicator());
}
if (controller.dailyNotesList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.note_alt_outlined,
size: 80, color: Colors.grey.shade300),
const SizedBox(height: 10),
Text("لا توجد سجلات لهذا اليوم",
style: TextStyle(color: Colors.grey.shade600)),
],
),
);
}
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: controller.dailyNotesList.length,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final note = controller.dailyNotesList[index];
final String name = note['editor'] ?? note['name'] ?? 'Unknown';
final String phone = note['phone'] ?? note['phone'] ?? 'Unknown';
final String content = note['note'] ?? note['content'] ?? '';
final String time = note['createdAt'] ?? '';
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
border: Border(
right: BorderSide(
color: _getEmployeeColor(name), width: 4))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
radius: 14,
backgroundColor:
_getEmployeeColor(name).withOpacity(0.1),
child: Icon(Icons.person,
size: 16, color: _getEmployeeColor(name)),
),
const SizedBox(width: 8),
Text(
name.toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
fontSize: 14),
),
const SizedBox(width: 100),
InkWell(
onTap: () {
makePhoneCall('+$phone');
},
child: Row(
children: [
Text(
phone,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
fontSize: 14),
),
Icon(Icons.phone)
],
),
),
const SizedBox(width: 22),
Text(
time.split(' ').last, // عرض الوقت فقط
style: TextStyle(
color: Colors.grey.shade400, fontSize: 12),
textDirection: TextDirection.ltr,
),
],
),
],
),
const Divider(height: 20),
Text(
content,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
height: 1.5),
),
],
),
);
},
);
},
),
);
}
Color _getEmployeeColor(String name) {
String n = name.toLowerCase().trim();
if (n.contains('shahd')) return Colors.redAccent;
if (n.contains('mayar')) return Colors.amber.shade700;
if (n.contains('rama2')) return Colors.green;
if (n.contains('rama1')) return Colors.blue;
return Colors.blueGrey;
}
}

File diff suppressed because it is too large Load Diff