إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي

This commit is contained in:
Hamza-Ayed
2026-07-26 12:18:23 +03:00
parent 16feb51a75
commit 4b5235c35c
16 changed files with 72976 additions and 72556 deletions
+1 -1
View File
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7
_flutter.loader.load({ _flutter.loader.load({
serviceWorkerSettings: { serviceWorkerSettings: {
serviceWorkerVersion: "4190023422" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ serviceWorkerVersion: "578408601" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
} }
}); });
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -493,6 +494,7 @@ class _AdminHomePageState extends State<AdminHomePage>
), ),
), ),
), ),
_buildChartsSection(data, controller, cs),
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
), ),
@@ -500,6 +502,206 @@ class _AdminHomePageState extends State<AdminHomePage>
); );
} }
Widget _buildChartsSection(
dynamic data, DashboardController controller, ColorScheme cs) {
final completed = (data['completed_rides'] ?? 0).toDouble();
final cancelled = (data['cancelled_rides'] ?? 0).toDouble();
final total = completed + cancelled;
final completedPercent = total > 0 ? (completed / total * 100).toStringAsFixed(1) : '0.0';
final comfort = (data['comfort'] ?? 0).toDouble();
final speed = (data['speed'] ?? 0).toDouble();
final lady = (data['lady'] ?? 0).toDouble();
final maxBar = [comfort, speed, lady].reduce((a, b) => a > b ? a : b);
final barMaxY = maxBar > 0 ? (maxBar * 1.3).ceilToDouble() : 10.0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 10),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text('تحليل البيانات',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
)),
],
),
),
Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 200,
child: Stack(
alignment: Alignment.center,
children: [
PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: 35,
sections: [
PieChartSectionData(
value: completed,
color: cs.primary,
radius: 55,
title: '',
),
PieChartSectionData(
value: cancelled,
color: cs.error,
radius: 55,
title: '',
),
],
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$completedPercent%',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
total.toInt().toString(),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
],
),
],
),
),
),
const SizedBox(width: 8),
Expanded(
child: SizedBox(
height: 200,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: barMaxY,
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
getTooltipColor: (_) => cs.surfaceContainerHighest,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
rod.toY.toInt().toString(),
TextStyle(
color: cs.onSurface,
fontSize: 11,
fontWeight: FontWeight.w600,
),
);
},
),
),
titlesData: FlTitlesData(
show: true,
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
final labels = ['Comfort', 'Speed', 'Lady'];
if (value.toInt() < labels.length) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
labels[value.toInt()],
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
);
}
return const SizedBox.shrink();
},
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(
x: 0,
barRods: [
BarChartRodData(
toY: comfort,
color: cs.tertiary,
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
BarChartGroupData(
x: 1,
barRods: [
BarChartRodData(
toY: speed,
color: const Color(0xFFF59E0B),
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
BarChartGroupData(
x: 2,
barRods: [
BarChartRodData(
toY: lady,
color: const Color(0xFFF48FB1),
width: 28,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
),
],
),
),
),
),
],
),
),
],
),
);
}
Widget _buildHighlightCard(_HighlightData h, ColorScheme cs) { Widget _buildHighlightCard(_HighlightData h, ColorScheme cs) {
return GlassContainer( return GlassContainer(
width: 155, width: 155,
@@ -4,7 +4,6 @@ import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import '../../../constant/colors.dart';
import '../../../controller/admin/live_analytics_controller.dart'; import '../../../controller/admin/live_analytics_controller.dart';
class LiveAnalyticsPage extends StatelessWidget { class LiveAnalyticsPage extends StatelessWidget {
@@ -13,15 +12,16 @@ class LiveAnalyticsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final c = Get.put(LiveAnalyticsController()); final c = Get.put(LiveAnalyticsController());
final cs = Theme.of(context).colorScheme;
return DefaultTabController( return DefaultTabController(
length: 4, length: 4,
child: Scaffold( child: Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text('لوحة البيانات التفاعلية', title: const Text('لوحة البيانات التفاعلية',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
backgroundColor: AppColor.surface, backgroundColor: cs.surfaceContainerHighest,
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
actions: [ actions: [
@@ -34,13 +34,13 @@ class LiveAnalyticsPage extends StatelessWidget {
onPressed: () => c.fetchAll(), onPressed: () => c.fetchAll(),
), ),
], ],
bottom: const TabBar( bottom: TabBar(
isScrollable: true, isScrollable: true,
indicatorColor: AppColor.accent, indicatorColor: cs.primary,
labelColor: AppColor.accent, labelColor: cs.primary,
unselectedLabelColor: AppColor.textSecondary, unselectedLabelColor: cs.onSurfaceVariant,
labelStyle: TextStyle(fontWeight: FontWeight.w600, fontSize: 13), labelStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13),
tabs: [ tabs: const [
Tab(text: 'الخريطة التفاعلية', icon: Icon(Icons.map_rounded, size: 18)), Tab(text: 'الخريطة التفاعلية', icon: Icon(Icons.map_rounded, size: 18)),
Tab(text: 'السوق والتسعير', icon: Icon(Icons.trending_up_rounded, size: 18)), Tab(text: 'السوق والتسعير', icon: Icon(Icons.trending_up_rounded, size: 18)),
Tab(text: 'الأسطول والجودة', icon: Icon(Icons.local_taxi_rounded, size: 18)), Tab(text: 'الأسطول والجودة', icon: Icon(Icons.local_taxi_rounded, size: 18)),
@@ -51,8 +51,8 @@ class LiveAnalyticsPage extends StatelessWidget {
body: GetBuilder<LiveAnalyticsController>( body: GetBuilder<LiveAnalyticsController>(
builder: (ctrl) { builder: (ctrl) {
if (ctrl.isLoading) { if (ctrl.isLoading) {
return const Center( return Center(
child: CircularProgressIndicator(color: AppColor.accent), child: CircularProgressIndicator(color: cs.primary),
); );
} }
if (ctrl.allData.isEmpty) { if (ctrl.allData.isEmpty) {
@@ -60,16 +60,16 @@ class LiveAnalyticsPage extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.cloud_off_rounded, Icon(Icons.cloud_off_rounded,
size: 48, color: AppColor.textMuted), size: 48, color: cs.onSurfaceVariant),
const SizedBox(height: 12), const SizedBox(height: 12),
Text('لا توجد بيانات لتاريخ ${ctrl.selectedDate}', Text('لا توجد بيانات لتاريخ ${ctrl.selectedDate}',
style: const TextStyle(color: AppColor.textSecondary)), style: TextStyle(color: cs.onSurfaceVariant)),
const SizedBox(height: 8), const SizedBox(height: 8),
if (ctrl.availableDates.isNotEmpty) if (ctrl.availableDates.isNotEmpty)
Text('آخر تاريخ متاح: ${ctrl.availableDates.first}', Text('آخر تاريخ متاح: ${ctrl.availableDates.first}',
style: const TextStyle( style: TextStyle(
color: AppColor.accent, fontSize: 13)), color: cs.primary, fontSize: 13)),
], ],
), ),
); );
@@ -89,6 +89,7 @@ class LiveAnalyticsPage extends StatelessWidget {
} }
void _pickDate(BuildContext context, LiveAnalyticsController c) async { void _pickDate(BuildContext context, LiveAnalyticsController c) async {
final cs = Theme.of(context).colorScheme;
final picked = await showDatePicker( final picked = await showDatePicker(
context: context, context: context,
initialDate: DateTime.tryParse(c.selectedDate) ?? DateTime.now(), initialDate: DateTime.tryParse(c.selectedDate) ?? DateTime.now(),
@@ -96,7 +97,7 @@ class LiveAnalyticsPage extends StatelessWidget {
lastDate: DateTime.now(), lastDate: DateTime.now(),
builder: (ctx, child) => Theme( builder: (ctx, child) => Theme(
data: ThemeData.dark().copyWith( data: ThemeData.dark().copyWith(
colorScheme: const ColorScheme.dark(primary: AppColor.accent), colorScheme: ColorScheme.dark(primary: cs.primary),
), ),
child: child!, child: child!,
), ),
@@ -129,12 +130,13 @@ class _StatCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -143,14 +145,14 @@ class _StatCard extends StatelessWidget {
Row( Row(
children: [ children: [
if (icon != null) ...[ if (icon != null) ...[
Icon(icon, size: 16, color: AppColor.textMuted), Icon(icon, size: 16, color: cs.onSurfaceVariant),
const SizedBox(width: 6), const SizedBox(width: 6),
], ],
Expanded( Expanded(
child: Text(label, child: Text(label,
style: const TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: AppColor.textMuted, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis),
@@ -162,17 +164,17 @@ class _StatCard extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Text(value, child: Text(value,
style: const TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: AppColor.textPrimary)), color: cs.onSurface)),
), ),
if (sub != null) ...[ if (sub != null) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Text(sub!, Text(sub!,
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: subColor ?? AppColor.textSecondary), color: subColor ?? cs.onSurfaceVariant),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis),
], ],
@@ -187,13 +189,14 @@ class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.title); const _SectionTitle(this.title);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12, top: 8), padding: const EdgeInsets.only(bottom: 12, top: 8),
child: Text(title, child: Text(title,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColor.textPrimary)), color: cs.onSurface)),
); );
} }
} }
@@ -211,22 +214,23 @@ class _ChartCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(title, Text(title,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColor.textSecondary)), color: cs.onSurfaceVariant)),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox(height: height, child: child), SizedBox(height: height, child: child),
], ],
@@ -281,6 +285,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final cs = Theme.of(context).colorScheme;
final c = widget.ctrl; final c = widget.ctrl;
final rt = c.realtime; final rt = c.realtime;
@@ -293,7 +298,6 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
return ListView( return ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
// KPI Cards
GridView.count( GridView.count(
crossAxisCount: MediaQuery.of(context).size.width > 600 ? 5 : 2, crossAxisCount: MediaQuery.of(context).size.width > 600 ? 5 : 2,
shrinkWrap: true, shrinkWrap: true,
@@ -316,7 +320,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
label: 'إيرادات اليوم', label: 'إيرادات اليوم',
value: _fmt(revToday), value: _fmt(revToday),
sub: '${revChange >= 0 ? '+' : ''}${revChange.toStringAsFixed(1)}% عن الأمس', sub: '${revChange >= 0 ? '+' : ''}${revChange.toStringAsFixed(1)}% عن الأمس',
subColor: revChange >= 0 ? AppColor.success : AppColor.danger, subColor: revChange >= 0 ? const Color(0xFF10B981) : cs.error,
icon: Icons.attach_money_rounded, icon: Icons.attach_money_rounded,
), ),
_StatCard( _StatCard(
@@ -334,18 +338,18 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
const SizedBox(height: 20), const SizedBox(height: 20),
const _SectionTitle('خريطة فجوة العرض والطلب'), const _SectionTitle('خريطة فجوة العرض والطلب'),
_buildLayerToggles(), _buildLayerToggles(cs),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildMap(c), _buildMap(c, cs),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildHourlyChart(c), _buildHourlyChart(c, cs),
_buildFunnelChart(c), _buildFunnelChart(c, cs),
], ],
); );
} }
Widget _buildLayerToggles() { Widget _buildLayerToggles(ColorScheme cs) {
return Wrap( return Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
@@ -354,17 +358,17 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
(v) => setState(() => showGap = v)), (v) => setState(() => showGap = v)),
_LayerChip('كثافة الطلب', showHeatmap, Colors.orange, _LayerChip('كثافة الطلب', showHeatmap, Colors.orange,
(v) => setState(() => showHeatmap = v)), (v) => setState(() => showHeatmap = v)),
_LayerChip('مواقع الكباتن', showSupply, AppColor.success, _LayerChip('مواقع الكباتن', showSupply, const Color(0xFF10B981),
(v) => setState(() => showSupply = v)), (v) => setState(() => showSupply = v)),
_LayerChip('أسعار المنافسين', showPricing, AppColor.info, _LayerChip('أسعار المنافسين', showPricing, cs.tertiary,
(v) => setState(() => showPricing = v)), (v) => setState(() => showPricing = v)),
_LayerChip('أعلى المناطق', showZones, AppColor.accent, _LayerChip('أعلى المناطق', showZones, cs.primary,
(v) => setState(() => showZones = v)), (v) => setState(() => showZones = v)),
], ],
); );
} }
Widget _buildMap(LiveAnalyticsController c) { Widget _buildMap(LiveAnalyticsController c, ColorScheme cs) {
final gapCells = (c.gap['cells'] as List?) ?? []; final gapCells = (c.gap['cells'] as List?) ?? [];
final heatmapData = c.heatmap['data']; final heatmapData = c.heatmap['data'];
final pricingGrids = c.pricing['grids']; final pricingGrids = c.pricing['grids'];
@@ -382,8 +386,8 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
markers.add(CircleMarker( markers.add(CircleMarker(
point: LatLng(lat, lng), point: LatLng(lat, lng),
color: gap > 0 color: gap > 0
? Colors.redAccent.withOpacity(0.3 + intensity * 0.5) ? Colors.redAccent.withValues(alpha: 0.3 + intensity * 0.5)
: AppColor.success.withOpacity(0.4), : const Color(0xFF10B981).withValues(alpha: 0.4),
borderStrokeWidth: 0, borderStrokeWidth: 0,
radius: 12 + intensity * 8, radius: 12 + intensity * 8,
)); ));
@@ -399,7 +403,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
if (lat == 0 || lng == 0) continue; if (lat == 0 || lng == 0) continue;
markers.add(CircleMarker( markers.add(CircleMarker(
point: LatLng(lat, lng), point: LatLng(lat, lng),
color: Colors.orange.withOpacity(0.35), color: Colors.orange.withValues(alpha: 0.35),
borderStrokeWidth: 0, borderStrokeWidth: 0,
radius: 5, radius: 5,
)); ));
@@ -416,9 +420,9 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
if (lat == 0 || lng == 0) continue; if (lat == 0 || lng == 0) continue;
markers.add(CircleMarker( markers.add(CircleMarker(
point: LatLng(lat, lng), point: LatLng(lat, lng),
color: AppColor.success.withOpacity(0.5), color: const Color(0xFF10B981).withValues(alpha: 0.5),
borderStrokeWidth: 1, borderStrokeWidth: 1,
borderColor: Colors.white.withOpacity(0.4), borderColor: Colors.white.withValues(alpha: 0.4),
radius: 6 + supply.clamp(0, 10).toDouble(), radius: 6 + supply.clamp(0, 10).toDouble(),
)); ));
} }
@@ -435,9 +439,9 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
if (lat == 0 || lng == 0) continue; if (lat == 0 || lng == 0) continue;
markers.add(CircleMarker( markers.add(CircleMarker(
point: LatLng(lat, lng), point: LatLng(lat, lng),
color: AppColor.info.withOpacity(0.5), color: cs.tertiary.withValues(alpha: 0.5),
borderStrokeWidth: 1, borderStrokeWidth: 1,
borderColor: Colors.white.withOpacity(0.3), borderColor: Colors.white.withValues(alpha: 0.3),
radius: 8, radius: 8,
)); ));
} }
@@ -451,9 +455,9 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
if (lat == 0 || lng == 0) continue; if (lat == 0 || lng == 0) continue;
markers.add(CircleMarker( markers.add(CircleMarker(
point: LatLng(lat, lng), point: LatLng(lat, lng),
color: AppColor.accent.withOpacity(0.5), color: cs.primary.withValues(alpha: 0.5),
borderStrokeWidth: 2, borderStrokeWidth: 2,
borderColor: Colors.white.withOpacity(0.5), borderColor: Colors.white.withValues(alpha: 0.5),
radius: 10 + (rides / 10).clamp(0, 15).toDouble(), radius: 10 + (rides / 10).clamp(0, 15).toDouble(),
)); ));
} }
@@ -485,7 +489,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
); );
} }
Widget _buildHourlyChart(LiveAnalyticsController c) { Widget _buildHourlyChart(LiveAnalyticsController c, ColorScheme cs) {
final hours = (c.hourly['hours'] as List?) ?? []; final hours = (c.hourly['hours'] as List?) ?? [];
if (hours.isEmpty) return const SizedBox.shrink(); if (hours.isEmpty) return const SizedBox.shrink();
@@ -497,7 +501,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
return BarChartGroupData(x: e.key, barRods: [ return BarChartGroupData(x: e.key, barRods: [
BarChartRodData( BarChartRodData(
toY: _toDouble(e.value['rides']), toY: _toDouble(e.value['rides']),
color: AppColor.accent.withOpacity(0.7), color: cs.primary.withValues(alpha: 0.7),
width: 10, width: 10,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)), borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
), ),
@@ -511,8 +515,8 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
final i = v.toInt(); final i = v.toInt();
if (i < 0 || i >= hours.length) return const SizedBox(); if (i < 0 || i >= hours.length) return const SizedBox();
return Text('${hours[i]['hour']}', return Text('${hours[i]['hour']}',
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -530,17 +534,17 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
); );
} }
Widget _buildFunnelChart(LiveAnalyticsController c) { Widget _buildFunnelChart(LiveAnalyticsController c, ColorScheme cs) {
final statuses = (c.funnel['statuses'] as List?) ?? []; final statuses = (c.funnel['statuses'] as List?) ?? [];
if (statuses.isEmpty) return const SizedBox.shrink(); if (statuses.isEmpty) return const SizedBox.shrink();
final colors = { final colors = {
'Finished': AppColor.success, 'Finished': const Color(0xFF10B981),
'wait': AppColor.warning, 'wait': cs.tertiary,
'started': AppColor.info, 'started': cs.tertiary,
'arrived': AppColor.accent, 'arrived': cs.primary,
'cancelled': AppColor.danger, 'cancelled': cs.error,
'timeout': AppColor.textMuted, 'timeout': cs.onSurfaceVariant,
}; };
return _ChartCard( return _ChartCard(
@@ -557,7 +561,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white), color: Colors.white),
color: colors[status] ?? AppColor.textMuted, color: colors[status] ?? cs.onSurfaceVariant,
radius: 60, radius: 60,
); );
}).toList(), }).toList(),
@@ -579,18 +583,19 @@ class _LayerChip extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return FilterChip( return FilterChip(
label: Text(label, label: Text(label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: active ? Colors.white : AppColor.textSecondary)), color: active ? Colors.white : cs.onSurfaceVariant)),
selected: active, selected: active,
onSelected: onChanged, onSelected: onChanged,
selectedColor: color.withOpacity(0.3), selectedColor: color.withValues(alpha: 0.3),
checkmarkColor: color, checkmarkColor: color,
backgroundColor: AppColor.surface, backgroundColor: cs.surfaceContainerHighest,
side: BorderSide( side: BorderSide(
color: active ? color.withOpacity(0.5) : AppColor.divider), color: active ? color.withValues(alpha: 0.5) : cs.outline),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
); );
} }
@@ -606,6 +611,7 @@ class _MarketTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final mkt = ctrl.market; final mkt = ctrl.market;
final countries = mkt['countries'] as Map<String, dynamic>? ?? {}; final countries = mkt['countries'] as Map<String, dynamic>? ?? {};
String latestPci = '—'; String latestPci = '—';
@@ -653,14 +659,14 @@ class _MarketTab extends StatelessWidget {
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCompetitorChart(), _buildCompetitorChart(cs),
_buildPciChart(countries), _buildPciChart(countries, cs),
_buildMarketShareChart(countries), _buildMarketShareChart(countries, cs),
], ],
); );
} }
Widget _buildCompetitorChart() { Widget _buildCompetitorChart(ColorScheme cs) {
final hourly = (ctrl.competitor['hourly'] as List?) ?? []; final hourly = (ctrl.competitor['hourly'] as List?) ?? [];
if (hourly.isEmpty) return const SizedBox.shrink(); if (hourly.isEmpty) return const SizedBox.shrink();
@@ -670,11 +676,11 @@ class _MarketTab extends StatelessWidget {
} }
final compColors = [ final compColors = [
AppColor.danger, cs.error,
AppColor.info, cs.tertiary,
AppColor.warning, cs.tertiary,
AppColor.success, const Color(0xFF10B981),
AppColor.accent, cs.primary,
]; ];
final hours = <String>{}; final hours = <String>{};
for (var r in hourly) { for (var r in hourly) {
@@ -720,8 +726,8 @@ class _MarketTab extends StatelessWidget {
final i = v.toInt(); final i = v.toInt();
if (i < 0 || i >= hourList.length) return const SizedBox(); if (i < 0 || i >= hourList.length) return const SizedBox();
return Text(hourList[i], return Text(hourList[i],
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -736,7 +742,7 @@ class _MarketTab extends StatelessWidget {
show: true, show: true,
drawVerticalLine: false, drawVerticalLine: false,
getDrawingHorizontalLine: (_) => getDrawingHorizontalLine: (_) =>
FlLine(color: AppColor.divider, strokeWidth: 0.5), FlLine(color: cs.outline, strokeWidth: 0.5),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
), ),
@@ -744,7 +750,7 @@ class _MarketTab extends StatelessWidget {
); );
} }
Widget _buildPciChart(Map<String, dynamic> countries) { Widget _buildPciChart(Map<String, dynamic> countries, ColorScheme cs) {
if (countries.isEmpty) return const SizedBox.shrink(); if (countries.isEmpty) return const SizedBox.shrink();
final rows = (countries.values.first as List?) ?? []; final rows = (countries.values.first as List?) ?? [];
if (rows.isEmpty) return const SizedBox.shrink(); if (rows.isEmpty) return const SizedBox.shrink();
@@ -759,13 +765,13 @@ class _MarketTab extends StatelessWidget {
return FlSpot( return FlSpot(
e.key.toDouble(), _toDouble(e.value['average_pci'])); e.key.toDouble(), _toDouble(e.value['average_pci']));
}).toList(), }).toList(),
color: AppColor.accent, color: cs.primary,
barWidth: 2, barWidth: 2,
dotData: const FlDotData(show: true), dotData: const FlDotData(show: true),
isCurved: true, isCurved: true,
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
color: AppColor.accent.withOpacity(0.08), color: cs.primary.withValues(alpha: 0.08),
), ),
), ),
], ],
@@ -778,8 +784,8 @@ class _MarketTab extends StatelessWidget {
if (i < 0 || i >= rows.length) return const SizedBox(); if (i < 0 || i >= rows.length) return const SizedBox();
final d = rows[i]['report_date']?.toString() ?? ''; final d = rows[i]['report_date']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -797,7 +803,7 @@ class _MarketTab extends StatelessWidget {
); );
} }
Widget _buildMarketShareChart(Map<String, dynamic> countries) { Widget _buildMarketShareChart(Map<String, dynamic> countries, ColorScheme cs) {
if (countries.isEmpty) return const SizedBox.shrink(); if (countries.isEmpty) return const SizedBox.shrink();
final rows = (countries.values.first as List?) ?? []; final rows = (countries.values.first as List?) ?? [];
if (rows.isEmpty) return const SizedBox.shrink(); if (rows.isEmpty) return const SizedBox.shrink();
@@ -810,7 +816,7 @@ class _MarketTab extends StatelessWidget {
return BarChartGroupData(x: e.key, barRods: [ return BarChartGroupData(x: e.key, barRods: [
BarChartRodData( BarChartRodData(
toY: _toDouble(e.value['market_share_percent']), toY: _toDouble(e.value['market_share_percent']),
color: const Color(0xFF00CEC9).withOpacity(0.7), color: const Color(0xFF00CEC9).withValues(alpha: 0.7),
width: 14, width: 14,
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(4)), const BorderRadius.vertical(top: Radius.circular(4)),
@@ -826,8 +832,8 @@ class _MarketTab extends StatelessWidget {
if (i < 0 || i >= rows.length) return const SizedBox(); if (i < 0 || i >= rows.length) return const SizedBox();
final d = rows[i]['report_date']?.toString() ?? ''; final d = rows[i]['report_date']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -856,6 +862,7 @@ class _FleetTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final rt = ctrl.realtime; final rt = ctrl.realtime;
final complaintsList = (ctrl.complaints['by_type'] as List?) ?? []; final complaintsList = (ctrl.complaints['by_type'] as List?) ?? [];
final totalComplaints = final totalComplaints =
@@ -892,27 +899,27 @@ class _FleetTab extends StatelessWidget {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildComplaintsChart(complaintsList), _buildComplaintsChart(complaintsList, cs),
_buildRevenueProfitChart(), _buildRevenueProfitChart(cs),
if (topZones.isNotEmpty) ...[ if (topZones.isNotEmpty) ...[
const _SectionTitle('أعلى المناطق نشاطاً (آخر 4 أسابيع)'), const _SectionTitle('أعلى المناطق نشاطاً (آخر 4 أسابيع)'),
_buildZonesTable(topZones), _buildZonesTable(topZones, cs),
], ],
], ],
); );
} }
Widget _buildComplaintsChart(List complaintsList) { Widget _buildComplaintsChart(List complaintsList, ColorScheme cs) {
if (complaintsList.isEmpty) return const SizedBox.shrink(); if (complaintsList.isEmpty) return const SizedBox.shrink();
final colors = [ final colors = [
AppColor.danger, cs.error,
AppColor.warning, cs.tertiary,
AppColor.info, cs.tertiary,
AppColor.accent, cs.primary,
AppColor.success, const Color(0xFF10B981),
AppColor.textMuted, cs.onSurfaceVariant,
const Color(0xFFfd79a8), const Color(0xFFfd79a8),
]; ];
@@ -940,7 +947,7 @@ class _FleetTab extends StatelessWidget {
); );
} }
Widget _buildRevenueProfitChart() { Widget _buildRevenueProfitChart(ColorScheme cs) {
final daily = (ctrl.revenue['daily'] as List?) ?? []; final daily = (ctrl.revenue['daily'] as List?) ?? [];
if (daily.isEmpty) return const SizedBox.shrink(); if (daily.isEmpty) return const SizedBox.shrink();
@@ -955,7 +962,7 @@ class _FleetTab extends StatelessWidget {
return FlSpot( return FlSpot(
e.key.toDouble(), _toDouble(e.value['total_revenue'])); e.key.toDouble(), _toDouble(e.value['total_revenue']));
}).toList(), }).toList(),
color: AppColor.info, color: cs.tertiary,
barWidth: 2, barWidth: 2,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
isCurved: true, isCurved: true,
@@ -965,7 +972,7 @@ class _FleetTab extends StatelessWidget {
return FlSpot( return FlSpot(
e.key.toDouble(), _toDouble(e.value['company_profit'])); e.key.toDouble(), _toDouble(e.value['company_profit']));
}).toList(), }).toList(),
color: AppColor.success, color: const Color(0xFF10B981),
barWidth: 2, barWidth: 2,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
isCurved: true, isCurved: true,
@@ -981,8 +988,8 @@ class _FleetTab extends StatelessWidget {
if (i < 0 || i >= daily.length) return const SizedBox(); if (i < 0 || i >= daily.length) return const SizedBox();
final d = daily[i]['date']?.toString() ?? ''; final d = daily[i]['date']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -997,7 +1004,7 @@ class _FleetTab extends StatelessWidget {
show: true, show: true,
drawVerticalLine: false, drawVerticalLine: false,
getDrawingHorizontalLine: (_) => getDrawingHorizontalLine: (_) =>
FlLine(color: AppColor.divider, strokeWidth: 0.5), FlLine(color: cs.outline, strokeWidth: 0.5),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
), ),
@@ -1005,50 +1012,50 @@ class _FleetTab extends StatelessWidget {
); );
} }
Widget _buildZonesTable(List topZones) { Widget _buildZonesTable(List topZones, ColorScheme cs) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: DataTable( child: DataTable(
columnSpacing: 20, columnSpacing: 20,
headingRowColor: WidgetStateProperty.all(AppColor.surfaceElevated), headingRowColor: WidgetStateProperty.all(cs.surfaceContainerHighest),
columns: const [ columns: [
DataColumn( const DataColumn(
label: Text('#', label: Text('#',
style: style:
TextStyle(fontSize: 12, color: AppColor.textSecondary))), TextStyle(fontSize: 12))),
DataColumn( const DataColumn(
label: Text('الموقع', label: Text('الموقع',
style: style:
TextStyle(fontSize: 12, color: AppColor.textSecondary))), TextStyle(fontSize: 12))),
DataColumn( const DataColumn(
label: Text('رحلات', label: Text('رحلات',
style: style:
TextStyle(fontSize: 12, color: AppColor.textSecondary))), TextStyle(fontSize: 12))),
DataColumn( const DataColumn(
label: Text('متوسط السعر', label: Text('متوسط السعر',
style: style:
TextStyle(fontSize: 12, color: AppColor.textSecondary))), TextStyle(fontSize: 12))),
], ],
rows: topZones.asMap().entries.map((e) { rows: topZones.asMap().entries.map((e) {
final z = e.value; final z = e.value;
return DataRow(cells: [ return DataRow(cells: [
DataCell(Text('${e.key + 1}', DataCell(Text('${e.key + 1}',
style: const TextStyle( style: const TextStyle(
fontSize: 12, color: AppColor.textPrimary))), fontSize: 12))),
DataCell(Text( DataCell(Text(
'${_toDouble(z['lat']).toStringAsFixed(4)}, ${_toDouble(z['lng']).toStringAsFixed(4)}', '${_toDouble(z['lat']).toStringAsFixed(4)}, ${_toDouble(z['lng']).toStringAsFixed(4)}',
style: const TextStyle( style: const TextStyle(
fontSize: 12, color: AppColor.textPrimary))), fontSize: 12))),
DataCell(Text(_fmt(z['rides']), DataCell(Text(_fmt(z['rides']),
style: const TextStyle( style: const TextStyle(
fontSize: 12, color: AppColor.textPrimary))), fontSize: 12))),
DataCell(Text(_toDouble(z['avg_price']).toStringAsFixed(2), DataCell(Text(_toDouble(z['avg_price']).toStringAsFixed(2),
style: const TextStyle( style: const TextStyle(
fontSize: 12, color: AppColor.textPrimary))), fontSize: 12))),
]); ]);
}).toList(), }).toList(),
), ),
@@ -1066,12 +1073,13 @@ class _GrowthTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final gr = ctrl.growth; final gr = ctrl.growth;
final totals = gr['totals'] as Map<String, dynamic>? ?? {}; final totals = gr['totals'] as Map<String, dynamic>? ?? {};
final weeks = (ctrl.weekly['weeks'] as List?) ?? []; final weeks = (ctrl.weekly['weeks'] as List?) ?? [];
String weekChange = ''; String weekChange = '';
Color weekColor = AppColor.textSecondary; Color weekColor = cs.onSurfaceVariant;
if (weeks.length >= 2) { if (weeks.length >= 2) {
final last = _toInt(weeks.last['rides']); final last = _toInt(weeks.last['rides']);
final prev = _toInt(weeks[weeks.length - 2]['rides']); final prev = _toInt(weeks[weeks.length - 2]['rides']);
@@ -1079,7 +1087,7 @@ class _GrowthTab extends StatelessWidget {
final pct = ((last - prev) / prev * 100).toStringAsFixed(1); final pct = ((last - prev) / prev * 100).toStringAsFixed(1);
weekChange = '${double.parse(pct) >= 0 ? '+' : ''}$pct% عن الأسبوع السابق'; weekChange = '${double.parse(pct) >= 0 ? '+' : ''}$pct% عن الأسبوع السابق';
weekColor = weekColor =
double.parse(pct) >= 0 ? AppColor.success : AppColor.danger; double.parse(pct) >= 0 ? const Color(0xFF10B981) : cs.error;
} }
} }
@@ -1115,15 +1123,15 @@ class _GrowthTab extends StatelessWidget {
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildGrowthChart(), _buildGrowthChart(cs),
_buildWeeklyChart(weeks), _buildWeeklyChart(weeks, cs),
_buildRetentionChart(), _buildRetentionChart(cs),
_buildRevenueChart(), _buildRevenueChart(cs),
], ],
); );
} }
Widget _buildGrowthChart() { Widget _buildGrowthChart(ColorScheme cs) {
final passengers = (ctrl.growth['passengers'] as List?) ?? []; final passengers = (ctrl.growth['passengers'] as List?) ?? [];
final drivers = (ctrl.growth['drivers'] as List?) ?? []; final drivers = (ctrl.growth['drivers'] as List?) ?? [];
if (passengers.isEmpty && drivers.isEmpty) return const SizedBox.shrink(); if (passengers.isEmpty && drivers.isEmpty) return const SizedBox.shrink();
@@ -1142,13 +1150,13 @@ class _GrowthTab extends StatelessWidget {
spots: passengers.asMap().entries.map((e) { spots: passengers.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), _toDouble(e.value['count'])); return FlSpot(e.key.toDouble(), _toDouble(e.value['count']));
}).toList(), }).toList(),
color: AppColor.accent, color: cs.primary,
barWidth: 2, barWidth: 2,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
isCurved: true, isCurved: true,
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
color: AppColor.accent.withOpacity(0.08), color: cs.primary.withValues(alpha: 0.08),
), ),
), ),
LineChartBarData( LineChartBarData(
@@ -1161,7 +1169,7 @@ class _GrowthTab extends StatelessWidget {
isCurved: true, isCurved: true,
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
color: const Color(0xFF00CEC9).withOpacity(0.08), color: const Color(0xFF00CEC9).withValues(alpha: 0.08),
), ),
), ),
], ],
@@ -1175,8 +1183,8 @@ class _GrowthTab extends StatelessWidget {
if (i < 0 || i >= passengers.length) return const SizedBox(); if (i < 0 || i >= passengers.length) return const SizedBox();
final d = passengers[i]['date']?.toString() ?? ''; final d = passengers[i]['date']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -1194,7 +1202,7 @@ class _GrowthTab extends StatelessWidget {
); );
} }
Widget _buildWeeklyChart(List weeks) { Widget _buildWeeklyChart(List weeks, ColorScheme cs) {
if (weeks.isEmpty) return const SizedBox.shrink(); if (weeks.isEmpty) return const SizedBox.shrink();
return _ChartCard( return _ChartCard(
@@ -1205,7 +1213,7 @@ class _GrowthTab extends StatelessWidget {
return BarChartGroupData(x: e.key, barRods: [ return BarChartGroupData(x: e.key, barRods: [
BarChartRodData( BarChartRodData(
toY: _toDouble(e.value['rides']), toY: _toDouble(e.value['rides']),
color: AppColor.accent.withOpacity(0.7), color: cs.primary.withValues(alpha: 0.7),
width: 12, width: 12,
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(4)), const BorderRadius.vertical(top: Radius.circular(4)),
@@ -1221,8 +1229,8 @@ class _GrowthTab extends StatelessWidget {
if (i < 0 || i >= weeks.length) return const SizedBox(); if (i < 0 || i >= weeks.length) return const SizedBox();
final d = weeks[i]['week_start']?.toString() ?? ''; final d = weeks[i]['week_start']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -1240,7 +1248,7 @@ class _GrowthTab extends StatelessWidget {
); );
} }
Widget _buildRetentionChart() { Widget _buildRetentionChart(ColorScheme cs) {
final cohorts = (ctrl.retention['cohorts'] as List?) ?? []; final cohorts = (ctrl.retention['cohorts'] as List?) ?? [];
if (cohorts.isEmpty) return const SizedBox.shrink(); if (cohorts.isEmpty) return const SizedBox.shrink();
@@ -1253,7 +1261,7 @@ class _GrowthTab extends StatelessWidget {
return BarChartGroupData(x: e.key, barRods: [ return BarChartGroupData(x: e.key, barRods: [
BarChartRodData( BarChartRodData(
toY: _toDouble(e.value['active_passengers']), toY: _toDouble(e.value['active_passengers']),
color: AppColor.accent.withOpacity(opacity), color: cs.primary.withValues(alpha: opacity),
width: 14, width: 14,
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(4)), const BorderRadius.vertical(top: Radius.circular(4)),
@@ -1266,8 +1274,8 @@ class _GrowthTab extends StatelessWidget {
showTitles: true, showTitles: true,
getTitlesWidget: (v, _) { getTitlesWidget: (v, _) {
return Text('w${v.toInt()}', return Text('w${v.toInt()}',
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -1285,7 +1293,7 @@ class _GrowthTab extends StatelessWidget {
); );
} }
Widget _buildRevenueChart() { Widget _buildRevenueChart(ColorScheme cs) {
final daily = (ctrl.revenue['daily'] as List?) ?? []; final daily = (ctrl.revenue['daily'] as List?) ?? [];
if (daily.isEmpty) return const SizedBox.shrink(); if (daily.isEmpty) return const SizedBox.shrink();
@@ -1300,13 +1308,13 @@ class _GrowthTab extends StatelessWidget {
return FlSpot( return FlSpot(
e.key.toDouble(), _toDouble(e.value['total_revenue'])); e.key.toDouble(), _toDouble(e.value['total_revenue']));
}).toList(), }).toList(),
color: AppColor.info, color: cs.tertiary,
barWidth: 2, barWidth: 2,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
isCurved: true, isCurved: true,
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
color: AppColor.info.withOpacity(0.08), color: cs.tertiary.withValues(alpha: 0.08),
), ),
), ),
], ],
@@ -1320,8 +1328,8 @@ class _GrowthTab extends StatelessWidget {
if (i < 0 || i >= daily.length) return const SizedBox(); if (i < 0 || i >= daily.length) return const SizedBox();
final d = daily[i]['date']?.toString() ?? ''; final d = daily[i]['date']?.toString() ?? '';
return Text(d.length >= 10 ? d.substring(5) : d, return Text(d.length >= 10 ? d.substring(5) : d,
style: const TextStyle( style: TextStyle(
fontSize: 9, color: AppColor.textMuted)); fontSize: 9, color: cs.onSurfaceVariant));
}, },
), ),
), ),
@@ -1336,7 +1344,7 @@ class _GrowthTab extends StatelessWidget {
show: true, show: true,
drawVerticalLine: false, drawVerticalLine: false,
getDrawingHorizontalLine: (_) => getDrawingHorizontalLine: (_) =>
FlLine(color: AppColor.divider, strokeWidth: 0.5), FlLine(color: cs.outline, strokeWidth: 0.5),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
), ),
@@ -16,19 +16,38 @@ class ComplaintListPage extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBar(
backgroundColor: cs.surface,
elevation: 0,
centerTitle: true,
title: Text('إدارة الشكاوى'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
leading: GestureDetector(
onTap: () => Get.back(),
child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
),
),
body: Column( body: Column(
children: [ children: [
Obx(() { Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('إدارة الشكاوى'.tr,
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
Expanded(
child: Column(
children: [
Obx(() {
if (controller.delayedComplaints.isNotEmpty) { if (controller.delayedComplaints.isNotEmpty) {
return Container( return Container(
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
@@ -85,7 +104,10 @@ class ComplaintListPage extends StatelessWidget {
), ),
], ],
), ),
); ),
],
),
);
} }
Widget _buildComplaintCard(BuildContext context, dynamic c) { Widget _buildComplaintCard(BuildContext context, dynamic c) {
@@ -504,28 +504,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true, backgroundColor: cs.surface,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
centerTitle: true,
title: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: cs.onSurface.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(12),
),
child: Text(
"نظام تتبع الكابتن",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: cs.onPrimary,
),
),
),
foregroundColor: cs.onPrimary,
),
body: Stack( body: Stack(
children: [ children: [
FlutterMap( FlutterMap(
@@ -542,6 +521,37 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
MarkerLayer(markers: _markers), MarkerLayer(markers: _markers),
], ],
), ),
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('نظام تتبع الكابتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
),
_buildDashboard(cs), _buildDashboard(cs),
], ],
), ),
@@ -241,28 +241,6 @@ class RideMonitorScreen extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(() {
if (controller.isTracking.value) {
return const SizedBox.shrink();
}
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: cs.onSurface),
title: Text(
"مراقبة الرحلات",
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}),
),
body: Obx(() { body: Obx(() {
if (!controller.isTracking.value) { if (!controller.isTracking.value) {
return _buildSearchForm(context, controller, cs); return _buildSearchForm(context, controller, cs);
@@ -274,108 +252,143 @@ class RideMonitorScreen extends StatelessWidget {
Widget _buildSearchForm( Widget _buildSearchForm(
BuildContext context, RideMonitorController controller, ColorScheme cs) { BuildContext context, RideMonitorController controller, ColorScheme cs) {
return Center( return Column(
child: SingleChildScrollView( children: [
physics: const BouncingScrollPhysics(), Container(
child: Padding( padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Row(
child: Container( children: [
padding: const EdgeInsets.all(32.0), GestureDetector(
decoration: BoxDecoration( onTap: () => Get.back(),
color: cs.surface, child: Container(
borderRadius: BorderRadius.circular(30), padding: const EdgeInsets.all(8),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.08),
blurRadius: 24,
offset: const Offset(0, 10),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.1), color: cs.surfaceContainerHighest,
shape: BoxShape.circle, borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
), ),
child: Icon(Icons.radar_rounded, size: 60, color: cs.primary), child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
), ),
const SizedBox(height: 24), ),
Text( const SizedBox(width: 12),
"تتبع رحلة نشطة", Text(
style: TextStyle( "مراقبة الرحلات",
color: cs.onSurface, style: TextStyle(
fontSize: 22, color: cs.onSurface,
fontWeight: FontWeight.bold, fontSize: 17,
), fontWeight: FontWeight.bold,
), ),
const SizedBox(height: 8), ),
Text( ],
"أدخل رقم هاتف السائق أو الراكب للبدء", ),
style: TextStyle( ),
color: cs.onSurfaceVariant, Expanded(
fontSize: 14, child: Center(
), child: SingleChildScrollView(
textAlign: TextAlign.center, physics: const BouncingScrollPhysics(),
), child: Padding(
const SizedBox(height: 32), padding: const EdgeInsets.symmetric(horizontal: 24.0),
Container( child: Container(
padding: const EdgeInsets.all(32.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: cs.surface, color: cs.surface,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(30),
border: Border.all(color: cs.outline, width: 2), boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.08),
blurRadius: 24,
offset: const Offset(0, 10),
)
],
), ),
child: TextField( child: Column(
controller: controller.phoneInputController, mainAxisSize: MainAxisSize.min,
keyboardType: TextInputType.phone, children: [
textDirection: TextDirection.ltr, Container(
style: TextStyle( padding: const EdgeInsets.all(20),
color: cs.onSurface, decoration: BoxDecoration(
fontSize: 16, color: cs.primary.withValues(alpha: 0.1),
fontWeight: FontWeight.w600, shape: BoxShape.circle,
), ),
decoration: InputDecoration( child: Icon(Icons.radar_rounded, size: 60, color: cs.primary),
hintText: "مثال: 0992952235...", ),
hintStyle: TextStyle(color: cs.onSurfaceVariant), const SizedBox(height: 24),
border: InputBorder.none, Text(
contentPadding: const EdgeInsets.symmetric( "تتبع رحلة نشطة",
vertical: 18, horizontal: 20), style: TextStyle(
prefixIcon: color: cs.onSurface,
Icon(Icons.phone_rounded, color: cs.primary), fontSize: 22,
), fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
"أدخل رقم هاتف السائق أو الراكب للبدء",
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 14,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline, width: 2),
),
child: TextField(
controller: controller.phoneInputController,
keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
),
decoration: InputDecoration(
hintText: "مثال: 0992952235...",
hintStyle: TextStyle(color: cs.onSurfaceVariant),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 18, horizontal: 20),
prefixIcon:
Icon(Icons.phone_rounded, color: cs.primary),
),
),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.startSearch,
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
"بدء المراقبة",
style: TextStyle(
color: cs.onPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
), ),
), ),
const SizedBox(height: 32), ),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.startSearch,
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
"بدء المراقبة",
style: TextStyle(
color: cs.onPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
), ),
), ),
), ),
), ],
); );
} }
@@ -141,7 +141,7 @@ class _EmployeeCard extends StatelessWidget {
border: Border.all(color: cs.outline.withValues(alpha: 0.1)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.2), color: cs.shadow.withValues(alpha: 0.2),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -227,7 +227,7 @@ class _EmployeeCard extends StatelessWidget {
cs.primary, cs), cs.primary, cs),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow(Icons.location_on_outlined, _buildInfoRow(Icons.location_on_outlined,
employee['site'] ?? 'غير محدد', Colors.blueGrey, cs), employee['site'] ?? 'غير محدد', cs.onSurfaceVariant, cs),
], ],
), ),
), ),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/constant/links.dart';
class HeatmapPage extends StatefulWidget { class HeatmapPage extends StatefulWidget {
@@ -100,27 +101,54 @@ class _HeatmapPageState extends State<HeatmapPage> {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBar(
title: const Text(
'خريطة النشاط الحرارية',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
),
backgroundColor: cs.surface,
foregroundColor: cs.onSurface,
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded),
tooltip: 'تحديث',
onPressed: _applyFilter,
),
],
),
body: Column( body: Column(
children: [ children: [
Container( Container(
color: cs.surfaceContainerHighest, padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
const Text('خريطة النشاط الحرارية',
style: TextStyle(
color: Colors.black,
fontSize: 17,
fontWeight: FontWeight.bold)),
const Spacer(),
GestureDetector(
onTap: _applyFilter,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.refresh_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
],
),
),
Expanded(
child: Column(
children: [
Container(
color: cs.surfaceContainerHighest,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column( child: Column(
children: [ children: [
_buildFilterRow( _buildFilterRow(
@@ -273,7 +301,10 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
], ],
), ),
); ),
],
),
);
} }
Widget _buildFilterRow({ Widget _buildFilterRow({
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import '../../../constant/colors.dart';
import '../../../controller/admin/marketing_controller.dart'; import '../../../controller/admin/marketing_controller.dart';
class MarketingPage extends StatelessWidget { class MarketingPage extends StatelessWidget {
@@ -481,7 +480,7 @@ class MarketingPage extends StatelessWidget {
hintText: 'سعر الكيلومتر المقترح', hintText: 'سعر الكيلومتر المقترح',
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 12), hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
filled: true, filled: true,
fillColor: AppColor.surfaceElevated, fillColor: cs.surfaceContainerLow,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
), ),
@@ -737,7 +736,7 @@ class MarketingPage extends StatelessWidget {
style: TextStyle(fontSize: 12, color: cs.onSurface), style: TextStyle(fontSize: 12, color: cs.onSurface),
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: AppColor.surfaceElevated, fillColor: cs.surfaceContainerLow,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: cs.outline), borderSide: BorderSide(color: cs.outline),
@@ -806,11 +805,11 @@ class MarketingPage extends StatelessWidget {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
AppColor.surfaceElevated, cs.surfaceContainerLow,
cs.surfaceContainerHighest, cs.surfaceContainerHighest,
], ],
), ),
border: Border.all(color: AppColor.accentBorder), border: Border.all(color: cs.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: cs.primary.withValues(alpha: 0.08), color: cs.primary.withValues(alpha: 0.08),
@@ -46,23 +46,50 @@ class SocialIntelligenceScreen extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBar( body: Column(
title: Text( children: [
'استخبارات السوشيال ميديا (Social Intelligence)', Container(
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: cs.onSurface), padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
), child: Row(
backgroundColor: cs.surface, children: [
elevation: 0, GestureDetector(
foregroundColor: cs.onSurface, onTap: () => Get.back(),
iconTheme: IconThemeData(color: cs.onSurface), child: Container(
actions: [ padding: const EdgeInsets.all(8),
IconButton( decoration: BoxDecoration(
icon: Icon(Icons.refresh, color: cs.onSurface), color: cs.surfaceContainerHighest,
onPressed: () => controller.fetchReports(), borderRadius: BorderRadius.circular(10),
) border: Border.all(color: cs.outline),
], ),
), child: Icon(Icons.arrow_back_ios_new_rounded,
body: Obx(() { color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('استخبارات السوشيال ميديا',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
const Spacer(),
GestureDetector(
onTap: () => controller.fetchReports(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.refresh,
color: cs.onSurfaceVariant, size: 16),
),
),
],
),
),
Expanded(
child: Obx(() {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return Center( return Center(
child: CircularProgressIndicator(color: cs.primary)); child: CircularProgressIndicator(color: cs.primary));
@@ -135,6 +162,9 @@ class SocialIntelligenceScreen extends StatelessWidget {
}, },
); );
}), }),
); ),
],
),
);
} }
} }
+60 -67
View File
@@ -17,8 +17,62 @@ class PackageUpdateScreen extends StatelessWidget {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: _buildAppBar(cs), body: Column(
body: GetBuilder<PackageController>( children: [
Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
),
child: Icon(Icons.system_update_rounded,
color: cs.primary, size: 16),
),
const SizedBox(width: 10),
Text('تحديث التطبيق',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
const Spacer(),
GestureDetector(
onTap: () => packageController.fetchPackages(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.refresh_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
],
),
),
Container(height: 1, color: cs.outline),
Expanded(
child: GetBuilder<PackageController>(
builder: (controller) { builder: (controller) {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return Center( return Center(
@@ -41,71 +95,10 @@ class PackageUpdateScreen extends StatelessWidget {
); );
}, },
), ),
); ),
} ],
),
PreferredSizeWidget _buildAppBar(ColorScheme cs) { );
return AppBar(
backgroundColor: cs.surface,
elevation: 0,
surfaceTintColor: Colors.transparent,
leading: GestureDetector(
onTap: () => Get.back(),
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
title: Row(
children: [
Container(
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(9),
border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
),
child: Icon(Icons.system_update_rounded,
color: cs.primary, size: 16),
),
const SizedBox(width: 10),
Text(
'تحديث التطبيق',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
],
),
actions: [
GestureDetector(
onTap: () => packageController.fetchPackages(),
child: Container(
margin: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.refresh_rounded,
color: cs.onSurfaceVariant, size: 18),
),
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: cs.outline),
),
);
} }
Widget _buildPackageCard( Widget _buildPackageCard(
@@ -16,7 +16,6 @@ class Passengrs extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
String myPhone = box.read(BoxName.adminPhone).toString(); String myPhone = box.read(BoxName.adminPhone).toString();
bool isSuperAdmin = bool isSuperAdmin =
@@ -16,45 +16,67 @@ class KazanEditorPage extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBar(
backgroundColor: cs.surface,
elevation: 0,
centerTitle: true,
title: Text('تعديل أسعار كازان'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
leading: GestureDetector(
onTap: () => Get.back(),
child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
),
),
body: Column( body: Column(
children: [ children: [
_buildCountryDropdown(cs), Container(
Expanded( padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty child: Row(
? Center(child: CircularProgressIndicator(color: cs.primary)) children: [
: SingleChildScrollView( GestureDetector(
padding: const EdgeInsets.all(16), onTap: () => Get.back(),
child: Column( child: Container(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(8),
children: [ decoration: BoxDecoration(
_buildSectionHeader('⚙️ الإعدادات العامة', cs), color: cs.surfaceContainerHighest,
_buildGeneralSettings(cs), borderRadius: BorderRadius.circular(10),
const SizedBox(height: 24), border: Border.all(color: cs.outline),
_buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة', cs),
_buildKmPricesGrid(cs),
const SizedBox(height: 24),
_buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)', cs),
_buildMinutePrices(cs),
const SizedBox(height: 32),
MyElevatedButton(
title: '💾 حفظ جميع التعديلات',
icon: Icons.save_rounded,
onPressed: () => _handleSave(),
),
const SizedBox(height: 100),
],
), ),
)), child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('تعديل أسعار كازان'.tr,
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
Expanded(
child: Column(
children: [
_buildCountryDropdown(cs),
Expanded(
child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty
? Center(child: CircularProgressIndicator(color: cs.primary))
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionHeader('⚙️ الإعدادات العامة', cs),
_buildGeneralSettings(cs),
const SizedBox(height: 24),
_buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة', cs),
_buildKmPricesGrid(cs),
const SizedBox(height: 24),
_buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)', cs),
_buildMinutePrices(cs),
const SizedBox(height: 32),
MyElevatedButton(
title: '💾 حفظ جميع التعديلات',
icon: Icons.save_rounded,
onPressed: () => _handleSave(),
),
const SizedBox(height: 100),
],
),
)),
),
],
),
), ),
], ],
), ),
@@ -11,15 +11,36 @@ class AuditLogsPage extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBar( body: Column(
title: Text('سجل العمليات (Audit Logs)', children: [
style: TextStyle(fontWeight: FontWeight.bold, color: cs.onSurface)), Container(
backgroundColor: cs.surface, padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
elevation: 0, child: Row(
centerTitle: true, children: [
iconTheme: IconThemeData(color: cs.onSurface), GestureDetector(
), onTap: () => Get.back(),
body: GetBuilder<SecurityV2Controller>( child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('سجل العمليات (Audit Logs)',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
Expanded(
child: GetBuilder<SecurityV2Controller>(
init: SecurityV2Controller(), init: SecurityV2Controller(),
builder: (ctrl) { builder: (ctrl) {
if (ctrl.isLoading) { if (ctrl.isLoading) {
@@ -57,7 +78,10 @@ class AuditLogsPage extends StatelessWidget {
); );
}, },
), ),
); ),
],
),
);
} }
Widget _buildLogItem(Map<String, dynamic> log, ColorScheme cs) { Widget _buildLogItem(Map<String, dynamic> log, ColorScheme cs) {
@@ -8,7 +8,6 @@ class AdvancedAnalyticsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.put(AnalyticsV2Controller());
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(