إصلاح توافق فلاتر مع Theme الجديد

This commit is contained in:
Hamza-Ayed
2026-07-26 11:33:20 +03:00
parent 7830efead7
commit 3af99cc18a
31 changed files with 4256 additions and 2833 deletions
+1 -1
View File
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7
_flutter.loader.load({ _flutter.loader.load({
serviceWorkerSettings: { serviceWorkerSettings: {
serviceWorkerVersion: "658515598" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ serviceWorkerVersion: "1676594277" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
} }
}); });
@@ -8,7 +8,6 @@ import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart';
import 'package:siro_admin/views/admin/quality/blacklist_page.dart'; import 'package:siro_admin/views/admin/quality/blacklist_page.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/theme.dart';
import '../../controller/admin/dashboard_controller.dart'; import '../../controller/admin/dashboard_controller.dart';
import '../../controller/admin/static_controller.dart'; import '../../controller/admin/static_controller.dart';
import '../../controller/functions/crud.dart'; import '../../controller/functions/crud.dart';
+257 -197
View File
@@ -2,10 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mycircular.dart';
import '../../../controller/admin/captain_admin_controller.dart'; import '../../../controller/admin/captain_admin_controller.dart';
import '../../../main.dart';
import 'captain_details.dart'; import 'captain_details.dart';
class CaptainsPage extends StatelessWidget { class CaptainsPage extends StatelessWidget {
@@ -21,159 +19,84 @@ class CaptainsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return MyScafolld( return Scaffold(
title: 'Search for Captain'.tr, backgroundColor: cs.surface,
isleading: true, body: Column(
body: [ children: [
Container( _buildAppBar(context, cs),
height: MediaQuery.of(context).size.height, _buildSearchSection(context, cs),
decoration: BoxDecoration( Expanded(
gradient: LinearGradient( child: GetBuilder<CaptainAdminController>(
begin: Alignment.topCenter, builder: (controller) {
end: Alignment.bottomCenter, if (controller.isLoading) {
colors: [ return _buildLoadingState(cs);
Theme.of(context).primaryColor.withOpacity(0.03), }
Colors.white,
], final message = controller.captainData['message'];
if (message == null) {
return _buildEmptyState(cs);
}
final List<dynamic> captains =
message is List ? message : [message];
if (captains.isEmpty) {
return _buildEmptyState(cs);
}
return _buildResultsList(context, captains, cs);
},
), ),
), ),
child: Column(
children: [
_buildHeaderSection(context),
Expanded(
child: GetBuilder<CaptainAdminController>(
builder: (controller) {
if (controller.isLoading) {
return _buildLoadingState();
}
final message = controller.captainData['message'];
if (message == null) {
return _buildEmptyState();
}
// 🔥 الحل هنا: توحيد الشكل إلى List
final List<dynamic> captains =
message is List ? message : [message];
if (captains.isEmpty) {
return _buildEmptyState();
}
return _buildResultsList(context, captains);
},
),
),
],
),
),
],
);
}
// ================= HEADER =================
Widget _buildHeaderSection(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.manage_search_rounded,
color: Theme.of(context).primaryColor,
size: 24,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Find Captain'.tr,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
Text(
'Search by phone number'.tr,
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
],
),
],
),
const SizedBox(height: 20),
_buildModernSearchBar(context),
], ],
), ),
); );
} }
Widget _buildModernSearchBar(BuildContext context) { Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[50], color: cs.surface,
borderRadius: BorderRadius.circular(16), border: Border(bottom: BorderSide(color: cs.outline)),
border: Border.all(color: Colors.grey[200]!),
), ),
child: Row( child: Row(
children: [ children: [
Expanded( GestureDetector(
child: TextField( onTap: () => Get.back(),
controller: searchController, child: Container(
keyboardType: TextInputType.phone, padding: const EdgeInsets.all(8),
style: const TextStyle(fontSize: 15), decoration: BoxDecoration(
decoration: InputDecoration( color: cs.surfaceContainerHighest,
hintText: '0990000000'.tr, borderRadius: BorderRadius.circular(10),
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), border: Border.all(color: cs.outline),
prefixIcon: Icon(Icons.phone_android_rounded,
color: Colors.grey[400], size: 22),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
onSubmitted: (_) => _performSearch(), child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
), ),
), ),
Padding( const SizedBox(width: 12),
padding: const EdgeInsets.all(6.0), Container(
child: Material( padding: const EdgeInsets.all(8),
color: Theme.of(context).primaryColor, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), color: cs.primary.withValues(alpha: 0.12),
child: InkWell( borderRadius: BorderRadius.circular(10),
onTap: _performSearch, border: Border.all(
borderRadius: BorderRadius.circular(12), color: cs.primary.withValues(alpha: 0.25)),
child: const Padding( ),
padding: EdgeInsets.all(12), child: Icon(Icons.drive_eta_rounded,
child: Icon(Icons.search, color: Colors.white, size: 24), color: cs.primary, size: 18),
), ),
), const SizedBox(width: 10),
Text(
'قائمة الكباتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
), ),
), ),
], ],
@@ -181,6 +104,58 @@ class CaptainsPage extends StatelessWidget {
); );
} }
Widget _buildSearchSection(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.outline),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchController,
keyboardType: TextInputType.phone,
style: TextStyle(color: cs.onSurface, fontSize: 14),
cursorColor: cs.primary,
decoration: InputDecoration(
hintText: '0990000000',
hintStyle: TextStyle(
color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon: Icon(Icons.phone_android_rounded,
color: cs.onSurfaceVariant, size: 20),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 14),
),
onSubmitted: (_) => _performSearch(),
),
),
Padding(
padding: const EdgeInsets.all(6.0),
child: Material(
color: cs.primary,
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: _performSearch,
borderRadius: BorderRadius.circular(10),
child: const Padding(
padding: EdgeInsets.all(10),
child: Icon(Icons.search_rounded,
color: Colors.white, size: 20),
),
),
),
),
],
),
),
);
}
void _performSearch() { void _performSearch() {
final phone = searchController.text.trim(); final phone = searchController.text.trim();
if (phone.isNotEmpty) { if (phone.isNotEmpty) {
@@ -188,37 +163,98 @@ class CaptainsPage extends StatelessWidget {
} }
} }
// ================= RESULTS ================= Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري التحميل...',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildResultsList(BuildContext context, List<dynamic> captains) { Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.person_off_outlined,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد كباتن',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildResultsList(
BuildContext context, List<dynamic> captains, ColorScheme cs) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 12), padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
child: Row( child: Row(
children: [ children: [
Text( Container(
'Search Results'.tr, width: 3,
style: const TextStyle( height: 14,
fontSize: 16, decoration: BoxDecoration(
fontWeight: FontWeight.w600, color: cs.primary,
color: Colors.black87, borderRadius: BorderRadius.circular(2),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(
'نتائج البحث',
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 10),
Expanded(
child: Container(height: 1, color: cs.outline),
),
const SizedBox(width: 8),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1), color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(10),
border: Border.all(
color: cs.primary.withValues(alpha: 0.2)),
), ),
child: Text( child: Text(
'${captains.length}', '${captains.length}',
style: TextStyle( style: TextStyle(
fontSize: 13, color: cs.primary,
fontWeight: FontWeight.bold, fontSize: 12,
color: Theme.of(context).primaryColor, fontWeight: FontWeight.w700,
), ),
), ),
), ),
@@ -228,12 +264,12 @@ class CaptainsPage extends StatelessWidget {
Expanded( Expanded(
child: ListView.separated( child: ListView.separated(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 0, 20, 80), padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
itemCount: captains.length, itemCount: captains.length,
separatorBuilder: (_, __) => const SizedBox(height: 12), separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final captain = captains[index] as Map<String, dynamic>; final captain = captains[index] as Map<String, dynamic>;
return _buildModernCaptainCard(context, captain); return _buildCaptainCard(context, captain, cs);
}, },
), ),
), ),
@@ -241,10 +277,10 @@ class CaptainsPage extends StatelessWidget {
); );
} }
// ================= CARD ================= Widget _buildCaptainCard(
BuildContext context,
Widget _buildModernCaptainCard( Map<String, dynamic> captain,
BuildContext context, Map<String, dynamic> captain) { ColorScheme cs) {
final String fullName = final String fullName =
'${captain['first_name'] ?? ''} ${captain['last_name'] ?? ''}'; '${captain['first_name'] ?? ''} ${captain['last_name'] ?? ''}';
final String phone = captain['phone']?.toString() ?? ''; final String phone = captain['phone']?.toString() ?? '';
@@ -252,67 +288,103 @@ class CaptainsPage extends StatelessWidget {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(16),
boxShadow: [ border: Border.all(color: cs.outline),
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(16),
onTap: () { onTap: () {
Get.to(() => const CaptainDetailsPage(), Get.to(() => const CaptainDetailsPage(),
arguments: {'data': captain}); arguments: {'data': captain});
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(14),
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 56, width: 52,
height: 56, height: 52,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [ colors: [
Theme.of(context).primaryColor.withOpacity(0.8), cs.primary.withValues(alpha: 0.8),
Theme.of(context).primaryColor, cs.primary,
], ],
), ),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
), ),
child: const Icon( child: const Icon(
Icons.person_rounded, Icons.person_rounded,
color: Colors.white, color: Colors.white,
size: 28, size: 26,
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 14),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
fullName, fullName,
style: const TextStyle( style: TextStyle(
fontSize: 16, color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text(phone), Row(
children: [
Icon(Icons.phone_rounded,
size: 13, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
phone,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontFamily: 'monospace',
),
),
],
),
if (isSuperAdmin && email != null) ...[ if (isSuperAdmin && email != null) ...[
const SizedBox(height: 4), const SizedBox(height: 3),
Text(email), Row(
children: [
Icon(Icons.email_outlined,
size: 12,
color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
email,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
], ],
], ],
), ),
), ),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: cs.onSurfaceVariant),
], ],
), ),
), ),
@@ -320,16 +392,4 @@ class CaptainsPage extends StatelessWidget {
), ),
); );
} }
// ================= STATES =================
Widget _buildLoadingState() {
return const Center(child: MyCircularProgressIndicator());
}
Widget _buildEmptyState() {
return const Center(
child: Text("No captains found"),
);
}
} }
@@ -1,14 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_admin/constant/box_name.dart'; import 'package:siro_admin/constant/box_name.dart';
import 'package:siro_admin/constant/info.dart';
import '../../../constant/info.dart'; import 'package:siro_admin/constant/char_map.dart';
import '../../../constant/char_map.dart'; import 'package:siro_admin/controller/drivers/driver_not_active_controller.dart';
import 'package:siro_admin/main.dart';
import '../../../controller/drivers/driver_not_active_controller.dart'; import 'package:siro_admin/print.dart';
import '../../../main.dart'; import 'package:siro_admin/constant/theme.dart';
import '../../../print.dart';
import 'driver_details_not_active_page.dart'; import 'driver_details_not_active_page.dart';
class DriversPendingPage extends StatelessWidget { class DriversPendingPage extends StatelessWidget {
@@ -18,35 +16,256 @@ class DriversPendingPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
controller.getDriversPending(); controller.getDriversPending();
Log.print( Log.print(
': ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'); ': ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}');
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text("Drivers Pending")), backgroundColor: cs.surface,
body: GetBuilder<DriverController>( body: Column(
id: 'drivers', children: [
builder: (c) { _buildAppBar(context, cs),
if (c.drivers.isEmpty) { Expanded(
return Center( child: GetBuilder<DriverController>(
child: const Text('no drivers found yet', id: 'drivers',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), builder: (c) {
); if (c.isLoading) {
} return _buildLoadingState(cs);
return ListView.builder( }
itemCount: c.drivers.length,
itemBuilder: (ctx, i) { if (c.drivers.isEmpty) {
final d = c.drivers[i]; return _buildEmptyState(cs);
return ListTile( }
title: Text(d["first_name"] + d['last_name'] ?? ""),
subtitle: Text(d["phone"] ?? ""), return RefreshIndicator(
onTap: () { onRefresh: () => controller.getDriversPending(),
Get.to(() => DriverDetailsPage(driverId: d["id"].toString())); color: cs.primary,
}, child: ListView.separated(
); padding: const EdgeInsets.fromLTRB(16, 8, 16, 80),
}, physics: const BouncingScrollPhysics(),
); itemCount: c.drivers.length,
}, separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (ctx, i) {
final d = c.drivers[i];
return _buildDriverCard(d, cs);
},
),
);
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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: const Color(0xFFF59E0B).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: Icon(Icons.hourglass_top_rounded,
color: const Color(0xFFF59E0B), size: 18),
),
const SizedBox(width: 10),
Text(
'طلبات الكباتن المعلقة',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل الطلبات...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.how_to_reg_rounded,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد طلبات تسجيل حالياً',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildDriverCard(dynamic driver, ColorScheme cs) {
final String fullName =
'${driver['first_name'] ?? ''} ${driver['last_name'] ?? ''}'.trim();
final String phone = driver['phone'] ?? '';
return Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Get.to(() => DriverDetailsPage(driverId: driver['id'].toString()));
},
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFFF59E0B).withValues(alpha: 0.20),
const Color(0xFFF59E0B).withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.2)),
),
child: Center(
child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D',
style: const TextStyle(
color: Color(0xFFF59E0B),
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fullName,
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
const SizedBox(height: 4),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B)
.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.hourglass_top_rounded,
size: 11,
color: const Color(0xFFF59E0B)),
const SizedBox(width: 3),
const Text(
'معلق',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFFF59E0B),
),
),
],
),
),
const SizedBox(width: 8),
Icon(Icons.phone_rounded,
size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
phone,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontFamily: 'monospace',
),
),
],
),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: cs.onSurfaceVariant),
],
),
),
),
), ),
); );
} }
@@ -1,9 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/complaint_controller.dart'; import '../../../controller/admin/complaint_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
import '../../widgets/my_textField.dart'; import '../../widgets/my_textField.dart';
@@ -14,81 +12,101 @@ class ComplaintListPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld( final cs = Theme.of(context).colorScheme;
title: 'إدارة الشكاوى'.tr,
isleading: true, return Scaffold(
body: [ backgroundColor: cs.surface,
Obx(() { appBar: AppBar(
if (controller.delayedComplaints.isNotEmpty) { backgroundColor: cs.surface,
return Container( elevation: 0,
margin: const EdgeInsets.all(16), centerTitle: true,
padding: const EdgeInsets.all(16), title: Text('إدارة الشكاوى'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
decoration: BoxDecoration( leading: GestureDetector(
color: AppColor.danger.withOpacity(0.1), onTap: () => Get.back(),
borderRadius: BorderRadius.circular(16), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
border: Border.all(color: AppColor.danger.withOpacity(0.3)), ),
), ),
child: Column( body: Column(
children: [ children: [
Row( Obx(() {
children: [ if (controller.delayedComplaints.isNotEmpty) {
const Icon(Icons.warning_amber_rounded, color: AppColor.danger), return Container(
const SizedBox(width: 12), margin: const EdgeInsets.all(16),
Expanded( padding: const EdgeInsets.all(16),
child: Text( decoration: BoxDecoration(
'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!', color: cs.error.withValues(alpha: 0.1),
style: AppStyle.body.copyWith(color: AppColor.danger, fontWeight: FontWeight.bold), borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.error.withValues(alpha: 0.3)),
),
child: Column(
children: [
Row(
children: [
Icon(Icons.warning_amber_rounded, color: cs.error),
const SizedBox(width: 12),
Expanded(
child: Text(
'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!',
style: GoogleFonts.inter(color: cs.error, fontSize: 14, fontWeight: FontWeight.bold),
),
), ),
), ],
], ),
), const SizedBox(height: 12),
const SizedBox(height: 12), Obx(() => MyElevatedButton(
Obx(() => MyElevatedButton( title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط',
title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط', onPressed: () => controller.showOnlyDelayed.toggle(),
onPressed: () => controller.showOnlyDelayed.toggle(), kolor: cs.error,
kolor: AppColor.danger, )),
)), ],
], ),
), );
); }
} return const SizedBox.shrink();
return const SizedBox.shrink(); }),
}), Expanded(
Obx(() { child: Obx(() {
final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList; final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList;
if (controller.isLoading.value && list.isEmpty) { if (controller.isLoading.value && list.isEmpty) {
return const Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator(color: cs.primary));
} }
return RefreshIndicator( return RefreshIndicator(
onRefresh: () => controller.getComplaints(), onRefresh: () => controller.getComplaints(),
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final complaint = list[index]; final complaint = list[index];
return _buildComplaintCard(context, complaint); return _buildComplaintCard(context, complaint);
}, },
), ),
); );
}), }),
], ),
],
),
); );
} }
Widget _buildComplaintCard(BuildContext context, dynamic c) { Widget _buildComplaintCard(BuildContext context, dynamic c) {
Color statusColor = _getStatusColor(c['statusComplaint']); final cs = Theme.of(context).colorScheme;
Color statusColor = _getStatusColor(c['statusComplaint'], cs);
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ExpansionTile( child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: _buildStatusIndicator(c['statusComplaint'], statusColor), leading: _buildStatusIndicator(c['statusComplaint'], statusColor, cs),
title: Text( title: Text(
c['description']?.toString() ?? 'بدون وصف', c['description']?.toString() ?? 'بدون وصف',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: AppStyle.title, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface),
), ),
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -96,18 +114,18 @@ class ComplaintListPage extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}', 'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}',
style: AppStyle.caption, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
Icon(Icons.person_rounded, size: 12, color: AppColor.textSecondary), Icon(Icons.person_rounded, size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text(c['passengerName'] ?? 'غير معروف', style: AppStyle.caption), Text(c['passengerName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(width: 12), const SizedBox(width: 12),
Icon(Icons.drive_eta_rounded, size: 12, color: AppColor.textSecondary), Icon(Icons.drive_eta_rounded, size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text(c['driverName'] ?? 'غير معروف', style: AppStyle.caption), Text(c['driverName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
], ],
), ),
], ],
@@ -118,12 +136,12 @@ class ComplaintListPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Divider(color: AppColor.divider), Divider(color: cs.outline),
_buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف'), _buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف', cs),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد'), _buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد', cs),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildRideDetails(c), _buildRideDetails(c, cs),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
children: [ children: [
@@ -131,7 +149,7 @@ class ComplaintListPage extends StatelessWidget {
child: MyElevatedButton( child: MyElevatedButton(
title: 'تحديث الحالة / حل الشكوى', title: 'تحديث الحالة / حل الشكوى',
onPressed: () => _showResolveDialog(context, c), onPressed: () => _showResolveDialog(context, c),
kolor: AppColor.accent, kolor: cs.primary,
), ),
), ),
], ],
@@ -144,14 +162,14 @@ class ComplaintListPage extends StatelessWidget {
); );
} }
Widget _buildStatusIndicator(String? status, Color color) { Widget _buildStatusIndicator(String? status, Color color, ColorScheme cs) {
return Container( return Container(
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.12), color: color.withValues(alpha: 0.12),
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: color.withOpacity(0.25)), border: Border.all(color: color.withValues(alpha: 0.25)),
), ),
child: Icon( child: Icon(
status == 'Resolved' ? Icons.check_circle_rounded : Icons.pending_rounded, status == 'Resolved' ? Icons.check_circle_rounded : Icons.pending_rounded,
@@ -161,33 +179,33 @@ class ComplaintListPage extends StatelessWidget {
); );
} }
Widget _buildInfoRow(String label, String value) { Widget _buildInfoRow(String label, String value, ColorScheme cs) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(label, style: AppStyle.caption.copyWith(color: AppColor.accent)), Text(label, style: GoogleFonts.inter(color: cs.primary, fontSize: 12)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text(value, style: AppStyle.body), Text(value, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14)),
], ],
); );
} }
Widget _buildRideDetails(dynamic c) { Widget _buildRideDetails(dynamic c, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surfaceElevated, 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(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildSmallStat('السعر', '${c['priceOfRide']} ل.س'), _buildSmallStat('السعر', '${c['priceOfRide']} ل.س', cs),
_buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★'), _buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★', cs),
_buildSmallStat('النوع', c['ascarType'] ?? 'N/A'), _buildSmallStat('النوع', c['ascarType'] ?? 'N/A', cs),
], ],
), ),
], ],
@@ -195,26 +213,27 @@ class ComplaintListPage extends StatelessWidget {
); );
} }
Widget _buildSmallStat(String label, String value) { Widget _buildSmallStat(String label, String value, ColorScheme cs) {
return Column( return Column(
children: [ children: [
Text(label, style: AppStyle.caption.copyWith(fontSize: 10)), Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(value, style: AppStyle.number.copyWith(fontSize: 12)), Text(value, style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, fontSize: 12, color: cs.onSurface)),
], ],
); );
} }
Color _getStatusColor(String? status) { Color _getStatusColor(String? status, ColorScheme cs) {
switch (status) { switch (status) {
case 'Open': return AppColor.danger; case 'Open': return cs.error;
case 'In Progress': return AppColor.warning; case 'In Progress': return cs.tertiary;
case 'Resolved': return AppColor.success; case 'Resolved': return const Color(0xFF10B981);
default: return AppColor.textSecondary; default: return cs.onSurfaceVariant;
} }
} }
void _showResolveDialog(BuildContext context, dynamic c) { void _showResolveDialog(BuildContext context, dynamic c) {
final cs = Theme.of(context).colorScheme;
final TextEditingController resController = TextEditingController(text: c['resolution']); final TextEditingController resController = TextEditingController(text: c['resolution']);
String selectedStatus = c['statusComplaint'] ?? 'Open'; String selectedStatus = c['statusComplaint'] ?? 'Open';
@@ -222,16 +241,16 @@ class ComplaintListPage extends StatelessWidget {
StatefulBuilder( StatefulBuilder(
builder: (context, setModalState) => Container( builder: (context, setModalState) => Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColor.surfaceElevated, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text('تحديث حالة الشكوى', style: AppStyle.headTitle), Text('تحديث حالة الشكوى', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildStatusDropdown(selectedStatus, (val) { _buildStatusDropdown(selectedStatus, cs, (val) {
setModalState(() => selectedStatus = val!); setModalState(() => selectedStatus = val!);
}), }),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -263,21 +282,21 @@ class ComplaintListPage extends StatelessWidget {
); );
} }
Widget _buildStatusDropdown(String current, Function(String?) onChanged) { Widget _buildStatusDropdown(String current, ColorScheme cs, Function(String?) onChanged) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 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: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<String>( child: DropdownButton<String>(
value: current, value: current,
isExpanded: true, isExpanded: true,
dropdownColor: AppColor.surfaceElevated, dropdownColor: cs.surfaceContainerHighest,
items: ['Open', 'In Progress', 'Resolved'] items: ['Open', 'In Progress', 'Resolved']
.map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: AppStyle.body))) .map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14))))
.toList(), .toList(),
onChanged: onChanged, onChanged: onChanged,
), ),
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/theme.dart';
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart'; import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:siro_admin/views/widgets/glass_container.dart'; import 'package:siro_admin/views/widgets/glass_container.dart';
@@ -1,9 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/driver_docs_controller.dart'; import '../../../controller/admin/driver_docs_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
import '../../widgets/snackbar.dart'; import '../../widgets/snackbar.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
@@ -15,67 +13,86 @@ class DriverDocsReviewPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld( final cs = Theme.of(context).colorScheme;
title: 'مراجعة طلبات التسجيل'.tr,
isleading: true, return Scaffold(
body: [ backgroundColor: cs.surface,
Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty appBar: AppBar(
? const Center(child: CircularProgressIndicator()) backgroundColor: cs.surface,
: controller.pendingDrivers.isEmpty elevation: 0,
? Center( centerTitle: true,
child: Column( title: Text('مراجعة طلبات التسجيل'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
mainAxisAlignment: MainAxisAlignment.center, leading: GestureDetector(
children: [ onTap: () => Get.back(),
Icon(Icons.how_to_reg_rounded, size: 64, color: AppColor.textMuted), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
const SizedBox(height: 16), ),
Text('لا يوجد طلبات تسجيل حالياً', style: AppStyle.subtitle), ),
], body: Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty
), ? Center(child: CircularProgressIndicator(color: cs.primary))
) : controller.pendingDrivers.isEmpty
: RefreshIndicator( ? Center(
onRefresh: () => controller.getPendingDrivers(), child: Column(
child: NotificationListener<ScrollNotification>( mainAxisAlignment: MainAxisAlignment.center,
onNotification: (ScrollNotification scrollInfo) { children: [
if (!controller.isLoading.value && Container(
!controller.isMoreLoading.value && padding: const EdgeInsets.all(20),
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 200) { decoration: BoxDecoration(shape: BoxShape.circle, color: cs.onSurfaceVariant.withValues(alpha: 0.1)),
controller.loadMore(); child: Icon(Icons.how_to_reg_rounded, size: 48, color: cs.onSurfaceVariant),
}
return false;
},
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.pendingDrivers.length + (controller.hasMore.value ? 1 : 0),
itemBuilder: (context, index) {
if (index == controller.pendingDrivers.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
);
}
final driver = controller.pendingDrivers[index];
return _buildDriverCard(context, driver);
},
), ),
const SizedBox(height: 16),
Text('لا يوجد طلبات تسجيل حالياً', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
)
: RefreshIndicator(
onRefresh: () => controller.getPendingDrivers(),
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (!controller.isLoading.value &&
!controller.isMoreLoading.value &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 200) {
controller.loadMore();
}
return false;
},
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.pendingDrivers.length + (controller.hasMore.value ? 1 : 0),
itemBuilder: (context, index) {
if (index == controller.pendingDrivers.length) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator(color: cs.primary)),
);
}
final driver = controller.pendingDrivers[index];
return _buildDriverCard(context, driver);
},
), ),
)), ),
], )),
); );
} }
Widget _buildDriverCard(BuildContext context, dynamic driver) { Widget _buildDriverCard(BuildContext context, dynamic driver) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: AppColor.accentSoft, backgroundColor: cs.primary.withValues(alpha: 0.1),
child: Text(driver['first_name']?[0] ?? 'D', style: const TextStyle(color: AppColor.accent)), child: Text(driver['first_name']?[0] ?? 'D', style: TextStyle(color: cs.primary)),
), ),
title: Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.title), title: Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
subtitle: Text(driver['phone'] ?? '', style: AppStyle.caption), subtitle: Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: AppColor.textSecondary), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16, color: cs.onSurfaceVariant),
onTap: () => _showDriverDetails(context, driver['id'].toString()), onTap: () => _showDriverDetails(context, driver['id'].toString()),
), ),
); );
@@ -88,45 +105,59 @@ class DriverDocsReviewPage extends StatelessWidget {
final driver = details['driver']; final driver = details['driver'];
final List docs = details['documents']; final List docs = details['documents'];
Get.to(() => MyScafolld( Get.to(() => Builder(builder: (ctx) {
title: 'تفاصيل السائق', final cs = Theme.of(ctx).colorScheme;
isleading: true, return Scaffold(
body: [ backgroundColor: cs.surface,
SingleChildScrollView( appBar: AppBar(
padding: const EdgeInsets.all(16), backgroundColor: cs.surface,
child: Column( elevation: 0,
crossAxisAlignment: CrossAxisAlignment.start, centerTitle: true,
children: [ title: Text('تفاصيل السائق', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
_buildDriverHeader(driver), leading: GestureDetector(
const SizedBox(height: 24), onTap: () => Get.back(),
Text('الوثائق المرفوعة', style: AppStyle.title), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
const SizedBox(height: 12), ),
...docs.map((doc) => _buildDocCard(doc)), ),
const SizedBox(height: 32), body: SingleChildScrollView(
MyElevatedButton( padding: const EdgeInsets.all(16),
title: 'اعتماد وتفعيل الحساب', child: Column(
icon: Icons.check_circle_rounded, crossAxisAlignment: CrossAxisAlignment.start,
kolor: AppColor.success, children: [
onPressed: () async { _buildDriverHeader(driver, cs),
bool success = await controller.approveDriver(id); const SizedBox(height: 24),
if (success) { Text('الوثائق المرفوعة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
Get.back(); const SizedBox(height: 12),
mySnackbarSuccess('تم تفعيل حساب السائق بنجاح'); ...docs.map((doc) => _buildDocCard(doc, cs)),
} const SizedBox(height: 32),
}, MyElevatedButton(
), title: 'اعتماد وتفعيل الحساب',
const SizedBox(height: 100), icon: Icons.check_circle_rounded,
], kolor: const Color(0xFF10B981),
onPressed: () async {
bool success = await controller.approveDriver(id);
if (success) {
Get.back();
mySnackbarSuccess('تم تفعيل حساب السائق بنجاح');
}
},
), ),
), const SizedBox(height: 100),
], ],
)); ),
),
);
}));
} }
Widget _buildDriverHeader(dynamic driver) { Widget _buildDriverHeader(dynamic driver, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: AppStyle.elevatedCard, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
),
child: Column( child: Column(
children: [ children: [
Row( Row(
@@ -135,28 +166,28 @@ class DriverDocsReviewPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.headTitle), Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)),
Text(driver['phone'] ?? '', style: AppStyle.subtitle), Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant)),
], ],
), ),
), ),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.warning.withOpacity(0.2), color: cs.tertiary.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Text('Pending', style: AppStyle.caption.copyWith(color: AppColor.warning)), child: Text('Pending', style: GoogleFonts.inter(color: cs.tertiary, fontSize: 12)),
), ),
], ],
), ),
const Divider(height: 32, color: AppColor.divider), Divider(height: 32, color: cs.outline),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A'), _buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A', cs),
_buildSmallInfo('الجنس', driver['gender'] ?? 'N/A'), _buildSmallInfo('الجنس', driver['gender'] ?? 'N/A', cs),
_buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A'), _buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A', cs),
], ],
), ),
], ],
@@ -164,27 +195,30 @@ class DriverDocsReviewPage extends StatelessWidget {
); );
} }
Widget _buildSmallInfo(String label, String value) { Widget _buildSmallInfo(String label, String value, ColorScheme cs) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(label, style: AppStyle.caption.copyWith(fontSize: 10)), Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(value, style: AppStyle.body.copyWith(fontSize: 12, fontWeight: FontWeight.bold)), Text(value, style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)),
], ],
); );
} }
Widget _buildDocCard(dynamic doc) { Widget _buildDocCard(dynamic doc, ColorScheme cs) {
String imageUrl = doc['link'] ?? ''; String imageUrl = doc['link'] ?? '';
// Ensure URL is absolute
if (!imageUrl.startsWith('http')) { if (!imageUrl.startsWith('http')) {
imageUrl = '${AppLink.server}/upload/drivers/$imageUrl'; imageUrl = '${AppLink.server}/upload/drivers/$imageUrl';
} }
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -192,9 +226,9 @@ class DriverDocsReviewPage extends StatelessWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.file_present_rounded, color: AppColor.accent, size: 20), Icon(Icons.file_present_rounded, color: cs.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(doc['doc_type'] ?? 'وثيقة', style: AppStyle.title.copyWith(fontSize: 14)), Text(doc['doc_type'] ?? 'وثيقة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 14, color: cs.onSurface)),
], ],
), ),
), ),
@@ -207,8 +241,8 @@ class DriverDocsReviewPage extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container( errorBuilder: (context, error, stackTrace) => Container(
height: 200, height: 200,
color: AppColor.surfaceElevated, color: cs.surfaceContainerHighest,
child: const Center(child: Icon(Icons.broken_image_rounded, size: 48, color: AppColor.textMuted)), child: Center(child: Icon(Icons.broken_image_rounded, size: 48, color: cs.onSurfaceVariant)),
), ),
), ),
), ),
@@ -3,30 +3,22 @@ import 'package:get/get.dart';
import 'package:siro_admin/controller/functions/crud.dart'; import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/functions/wallet.dart'; import 'package:siro_admin/controller/functions/wallet.dart';
import 'package:siro_admin/views/widgets/snackbar.dart'; import 'package:siro_admin/views/widgets/snackbar.dart';
import 'package:siro_admin/constant/links.dart';
import '../../../constant/links.dart'; // تأكد من المسار
// --- Controller: المسؤول عن المنطق (البحث، الفحص، الإضافة) ---
class DriverGiftCheckerController extends GetxController { class DriverGiftCheckerController extends GetxController {
// للتحكم في حقل النص
final TextEditingController phoneController = TextEditingController(); final TextEditingController phoneController = TextEditingController();
// لعرض النتائج وحالة التحميل
var statusLog = "".obs; var statusLog = "".obs;
var isLoading = false.obs; var isLoading = false.obs;
// قائمة السائقين (سنقوم بتحميلها للبحث عن الـ ID)
List<dynamic> driversCache = []; List<dynamic> driversCache = [];
// 1. تحميل قائمة السائقين لاستخراج الـ ID منها
Future<void> fetchDriverCache() async { Future<void> fetchDriverCache() async {
try { try {
final response = await CRUD().post( final response = await CRUD().post(
link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php', link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php',
payload: {'phone': phoneController.text.trim()}, payload: {'phone': phoneController.text.trim()},
); );
// print('response: ${response}');
if (response != 'failure') { if (response != 'failure') {
driversCache = (response['message']); driversCache = (response['message']);
@@ -36,7 +28,6 @@ class DriverGiftCheckerController extends GetxController {
} }
} }
// --- الدالة الرئيسية التي تنفذ العملية المطلوبة ---
Future<void> processDriverGift() async { Future<void> processDriverGift() async {
String phoneInput = phoneController.text.trim(); String phoneInput = phoneController.text.trim();
@@ -49,13 +40,11 @@ class DriverGiftCheckerController extends GetxController {
statusLog.value = "جاري البحث عن السائق..."; statusLog.value = "جاري البحث عن السائق...";
try { try {
// الخطوة 1: استخراج الـ ID بناءً على رقم الهاتف
var driver = driversCache.firstWhere( var driver = driversCache.firstWhere(
(d) { (d) {
String dbPhone = String dbPhone =
d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), ''); d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), '');
String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), ''); String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), '');
// قارن آخر 9 أرقام لتجاوز مشكلة 09 مقابل 963
if (dbPhone.length >= 9 && inputPhone.length >= 9) { if (dbPhone.length >= 9 && inputPhone.length >= 9) {
return dbPhone.substring(dbPhone.length - 9) == return dbPhone.substring(dbPhone.length - 9) ==
inputPhone.substring(inputPhone.length - 9); inputPhone.substring(inputPhone.length - 9);
@@ -77,17 +66,8 @@ class DriverGiftCheckerController extends GetxController {
statusLog.value = statusLog.value =
"✅ تم العثور على السائق: $driverName (ID: $driverId)\nجاري فحص رصيد الهدايا..."; "✅ تم العثور على السائق: $driverName (ID: $driverId)\nجاري فحص رصيد الهدايا...";
// الخطوة 2: فحص السيرفر هل الهدية موجودة؟
// bool hasGift = await _checkIfGiftExistsOnServer(driverId);
// if (hasGift) {
// statusLog.value +=
// "\n⚠️ هذا السائق لديه هدية الافتتاح (30,000) مسبقاً. لم يتم اتخاذ إجراء.";
// } else {
// الخطوة 3: إضافة الهدية
statusLog.value += "\n🎁 الهدية غير موجودة. جاري الإضافة..."; statusLog.value += "\n🎁 الهدية غير موجودة. جاري الإضافة...";
await _addGiftToDriver(driverId, phoneInput, "300"); await _addGiftToDriver(driverId, phoneInput, "300");
// }
} catch (e) { } catch (e) {
statusLog.value = "حدث خطأ غير متوقع: $e"; statusLog.value = "حدث خطأ غير متوقع: $e";
} finally { } finally {
@@ -95,130 +75,258 @@ class DriverGiftCheckerController extends GetxController {
} }
} }
// دالة إضافة الهدية باستخدام WalletController الموجود عندك
Future<void> _addGiftToDriver( Future<void> _addGiftToDriver(
String driverId, String phone, String amount) async { String driverId, String phone, String amount) async {
final wallet = Get.put(WalletController()); final wallet = Get.put(WalletController());
// استخدام الدالة الموجودة في نظامك
await wallet.addDrivergift300('new driver', driverId, amount, phone); await wallet.addDrivergift300('new driver', driverId, amount, phone);
// statusLog.value += "\n✅ تمت إضافة مبلغ $amount ل.س بنجاح!";
// إضافة تنبيه مرئي
// Get.snackbar("تم بنجاح", "تمت إضافة هدية الافتتاح للسائق",
// backgroundColor: Colors.green, colorText: Colors.white);
} }
} }
// --- View: واجهة المستخدم ---
class DriverGiftCheckPage extends StatelessWidget { class DriverGiftCheckPage extends StatelessWidget {
const DriverGiftCheckPage({super.key}); const DriverGiftCheckPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// حقن الكنترولر
final controller = Get.put(DriverGiftCheckerController()); final controller = Get.put(DriverGiftCheckerController());
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF8FAFC), backgroundColor: cs.surface,
appBar: AppBar( body: Column(
title: const Text("فحص ومنح هدية الافتتاح", children: [
style: TextStyle(fontWeight: FontWeight.bold)), _buildAppBar(context, cs),
backgroundColor: const Color(0xFF0F172A), // نفس لون الهيدر السابق Expanded(
foregroundColor: Colors.white, child: SingleChildScrollView(
), physics: const BouncingScrollPhysics(),
body: Padding( padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// كارد الإدخال
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10)
],
),
child: Column( child: Column(
children: [ children: [
const Icon(Icons.card_giftcard, _buildInputCard(context, controller, cs),
size: 50, color: Colors.amber), const SizedBox(height: 24),
const SizedBox(height: 10), _buildLogSection(controller, cs),
const Text(
"أدخل رقم الهاتف للتحقق",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
// حقل الإدخال
TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: 'مثال: 0912345678',
prefixIcon: const Icon(Icons.phone),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
filled: true,
fillColor: Colors.grey[50],
),
),
const SizedBox(height: 20),
// زر التنفيذ
Obx(() => SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: controller.isLoading.value
? null
: () => controller.processDriverGift(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0F172A),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
child: controller.isLoading.value
? const CircularProgressIndicator(
color: Colors.white)
: const Text("تحقق ومنح الهدية (30,000)",
style: TextStyle(fontSize: 16)),
),
)),
], ],
), ),
), ),
),
],
),
);
}
const SizedBox(height: 30), Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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: const Color(0xFFF59E0B).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: Icon(Icons.card_giftcard_rounded,
color: const Color(0xFFF59E0B), size: 18),
),
const SizedBox(width: 10),
Text(
'هدايا وعروض الكباتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
// منطقة عرض النتائج (Log) Widget _buildInputCard(
Expanded( BuildContext context, DriverGiftCheckerController controller, ColorScheme cs) {
child: Container( return Container(
width: double.infinity, padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(16), decoration: BoxDecoration(
decoration: BoxDecoration( color: cs.surfaceContainerHighest,
color: Colors.black87, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(12), border: Border.all(color: cs.outline),
), boxShadow: [
child: SingleChildScrollView( BoxShadow(
child: Obx(() => Text( color: cs.shadow.withValues(alpha: 0.06),
controller.statusLog.value.isEmpty blurRadius: 20,
? "بانتظار العملية..." offset: const Offset(0, 8),
: controller.statusLog.value, ),
style: const TextStyle( ],
color: Colors.greenAccent, ),
fontFamily: 'monospace', child: Column(
height: 1.5), children: [
)), Container(
), padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFFF59E0B).withValues(alpha: 0.20),
const Color(0xFFF59E0B).withValues(alpha: 0.08),
],
),
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: const Icon(Icons.card_giftcard_rounded,
size: 40, color: Color(0xFFF59E0B)),
),
const SizedBox(height: 16),
Text(
"أدخل رقم الهاتف للتحقق",
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
Text(
"سيتم التحقق ومنح هدية الافتتاح (300)",
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
),
),
const SizedBox(height: 20),
Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.outline),
),
child: TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
style: TextStyle(color: cs.onSurface, fontSize: 14),
cursorColor: cs.primary,
decoration: InputDecoration(
hintText: 'مثال: 0912345678',
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon: Icon(Icons.phone_rounded,
color: cs.onSurfaceVariant, size: 20),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
), ),
], ),
), const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 48,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading.value
? null
: () => controller.processDriverGift(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF59E0B),
foregroundColor: Colors.white,
disabledBackgroundColor: cs.outline,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14)),
elevation: 0,
),
child: controller.isLoading.value
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2),
)
: const Text("تحقق ومنح الهدية",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold)),
)),
),
],
),
);
}
Widget _buildLogSection(DriverGiftCheckerController controller, ColorScheme cs) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.terminal_rounded,
color: cs.primary, size: 14),
),
const SizedBox(width: 8),
Text(
'سجل العمليات',
style: TextStyle(
color: cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF0A0A0B),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
constraints: const BoxConstraints(minHeight: 200),
child: SingleChildScrollView(
child: Obx(() => Text(
controller.statusLog.value.isEmpty
? "بانتظار العملية..."
: controller.statusLog.value,
style: const TextStyle(
color: Color(0xFF4ADE80),
fontFamily: 'monospace',
fontSize: 12,
height: 1.6,
),
)),
),
),
],
), ),
); );
} }
@@ -21,30 +21,24 @@ class SiroTrackerScreen extends StatefulWidget {
class _SiroTrackerScreenState extends State<SiroTrackerScreen> class _SiroTrackerScreenState extends State<SiroTrackerScreen>
with TickerProviderStateMixin { with TickerProviderStateMixin {
// === Map Controller ===
final MapController _mapController = MapController(); final MapController _mapController = MapController();
List<Marker> _markers = []; List<Marker> _markers = [];
// === State Variables ===
bool isLiveMode = true; bool isLiveMode = true;
bool isLoading = false; bool isLoading = false;
String lastUpdated = "جاري التحميل..."; String lastUpdated = "جاري التحميل...";
// === Counters ===
int liveCount = 0; int liveCount = 0;
int dayCount = 0; int dayCount = 0;
Timer? _timer; Timer? _timer;
// === Animation Controllers ===
late AnimationController _fadeController; late AnimationController _fadeController;
late AnimationController _scaleController; late AnimationController _scaleController;
// === Admin Info ===
String myPhone = box.read(BoxName.adminPhone).toString(); String myPhone = box.read(BoxName.adminPhone).toString();
bool get isSuperAdmin => bool get isSuperAdmin =>
myPhone == '963942542053' || myPhone == '963992952235'; myPhone == '963942542053' || myPhone == '963992952235';
// === URLs ===
final String _baseDir = "${AppLink.server}/ride/location/"; final String _baseDir = "${AppLink.server}/ride/location/";
@override @override
@@ -90,10 +84,11 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
} }
void _showSnackBar(String message) { void _showSnackBar(String message) {
final cs = Theme.of(context).colorScheme;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message), content: Text(message),
backgroundColor: const Color(0xFF2C3E50), backgroundColor: cs.surfaceContainerHighest,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -160,6 +155,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
} }
void _buildMarkers(List<dynamic> drivers) { void _buildMarkers(List<dynamic> drivers) {
final cs = Theme.of(context).colorScheme;
List<Marker> newMarkers = []; List<Marker> newMarkers = [];
for (var d in drivers) { for (var d in drivers) {
@@ -192,7 +188,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
cancelled: cancelled, cancelled: cancelled,
); );
}, },
child: _buildMarkerWidget(heading), child: _buildMarkerWidget(heading, cs),
), ),
), ),
); );
@@ -203,7 +199,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
}); });
} }
Widget _buildMarkerWidget(double heading) { Widget _buildMarkerWidget(double heading, ColorScheme cs) {
return Stack( return Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@@ -214,17 +210,17 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( gradient: LinearGradient(
colors: isLiveMode colors: isLiveMode
? [const Color(0xFF27AE60), const Color(0xFF229954)] ? [const Color(0xFF10B981), const Color(0xFF10B981)]
: [const Color(0xFF3498DB), const Color(0xFF2980B9)], : [cs.tertiary, cs.tertiary],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: (isLiveMode color: (isLiveMode
? const Color(0xFF27AE60) ? const Color(0xFF10B981)
: const Color(0xFF3498DB)) : cs.tertiary)
.withOpacity(0.5), .withValues(alpha: 0.5),
blurRadius: 12, blurRadius: 12,
spreadRadius: 2, spreadRadius: 2,
) )
@@ -235,7 +231,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
angle: heading * (math.pi / 180), angle: heading * (math.pi / 180),
child: Icon( child: Icon(
Icons.navigation, Icons.navigation,
color: Colors.white, color: cs.onPrimary,
size: 26, size: 26,
), ),
), ),
@@ -252,6 +248,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
required String completed, required String completed,
required String cancelled, required String cancelled,
}) { }) {
final cs = Theme.of(context).colorScheme;
showDialog( showDialog(
context: context, context: context,
builder: (_) => Dialog( builder: (_) => Dialog(
@@ -260,14 +257,14 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.white, Colors.grey.shade50], colors: [cs.surface, cs.surfaceContainerHighest],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.15), color: cs.shadow.withValues(alpha: 0.15),
blurRadius: 30, blurRadius: 30,
spreadRadius: 5, spreadRadius: 5,
) )
@@ -284,38 +281,38 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: isLiveMode colors: isLiveMode
? [const Color(0xFF27AE60), const Color(0xFF229954)] ? [const Color(0xFF10B981), const Color(0xFF10B981)]
: [const Color(0xFF3498DB), const Color(0xFF2980B9)], : [cs.tertiary, cs.tertiary],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.person_outline, color: Colors.white, size: 20), Icon(Icons.person_outline, color: cs.onPrimary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
"معلومات الكابتن", "معلومات الكابتن",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: cs.onPrimary,
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildInfoCard(Icons.person, "الاسم", name), _buildInfoCard(Icons.person, "الاسم", name, cs),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoCard(Icons.badge, "المعرف", driverId), _buildInfoCard(Icons.badge, "المعرف", driverId, cs),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoCard(Icons.speed, "السرعة", "$speed كم/س"), _buildInfoCard(Icons.speed, "السرعة", "$speed كم/س", cs),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildStatsContainer(completed, cancelled), _buildStatsContainer(completed, cancelled, cs),
if (isSuperAdmin) ...[ if (isSuperAdmin) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
_buildPhoneButton(phone), _buildPhoneButton(phone, cs),
], ],
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
@@ -323,8 +320,8 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2C3E50), backgroundColor: cs.onSurface,
foregroundColor: Colors.white, foregroundColor: cs.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -348,19 +345,20 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
); );
} }
Widget _buildInfoCard(IconData icon, String label, String value) { Widget _buildInfoCard(
IconData icon, String label, String value, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: Colors.grey.shade200, color: cs.outline,
width: 1, width: 1,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: cs.shadow.withValues(alpha: 0.05),
blurRadius: 8, blurRadius: 8,
) )
], ],
@@ -370,10 +368,10 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade100, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Icon(icon, color: const Color(0xFF2C3E50), size: 20), child: Icon(icon, color: cs.onSurface, size: 20),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
@@ -383,17 +381,17 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
label, label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
value, value,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF2C3E50), color: cs.onSurface,
), ),
), ),
], ],
@@ -403,34 +401,36 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
); );
} }
Widget _buildStatsContainer(String completed, String cancelled) { Widget _buildStatsContainer(
String completed, String cancelled, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.grey.shade50, Colors.grey.shade100], colors: [cs.surfaceContainerHighest, cs.surfaceContainerHighest],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200), border: Border.all(color: cs.outline),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildStatItem("✓ مكتملة", completed, const Color(0xFF27AE60)), _buildStatItem("✓ مكتملة", completed, const Color(0xFF10B981), cs),
Container( Container(
width: 1, width: 1,
height: 40, height: 40,
color: Colors.grey.shade300, color: cs.outline,
), ),
_buildStatItem("✕ ملغاة", cancelled, const Color(0xFFE74C3C)), _buildStatItem("✕ ملغاة", cancelled, cs.error, cs),
], ],
), ),
); );
} }
Widget _buildStatItem(String label, String value, Color color) { Widget _buildStatItem(
String label, String value, Color color, ColorScheme cs) {
return Column( return Column(
children: [ children: [
Text( Text(
@@ -446,7 +446,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
label, label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -454,7 +454,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
); );
} }
Widget _buildPhoneButton(String phone) { Widget _buildPhoneButton(String phone, ColorScheme cs) {
return InkWell( return InkWell(
onTap: () { onTap: () {
if (phone.isNotEmpty) _makePhoneCall(phone); if (phone.isNotEmpty) _makePhoneCall(phone);
@@ -465,14 +465,14 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [const Color(0xFFFFA500), const Color(0xFFFF8C00)], colors: [cs.tertiary, cs.tertiary],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0xFFFFA500).withOpacity(0.4), color: cs.tertiary.withValues(alpha: 0.4),
blurRadius: 12, blurRadius: 12,
spreadRadius: 2, spreadRadius: 2,
) )
@@ -481,13 +481,13 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.call, color: Colors.white, size: 20), Icon(Icons.call, color: cs.onPrimary, size: 20),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
phone, phone,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: cs.onPrimary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 15, fontSize: 15,
), ),
@@ -502,6 +502,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: AppBar( appBar: AppBar(
@@ -511,20 +512,19 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
title: Container( title: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF2C3E50).withOpacity(0.9), color: cs.onSurface.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
// backdropFilter: const BackdropFilter(blur: 10),
), ),
child: const Text( child: Text(
"نظام تتبع الكابتن", "نظام تتبع الكابتن",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: cs.onPrimary,
), ),
), ),
), ),
foregroundColor: Colors.white, foregroundColor: cs.onPrimary,
), ),
body: Stack( body: Stack(
children: [ children: [
@@ -542,13 +542,13 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
MarkerLayer(markers: _markers), MarkerLayer(markers: _markers),
], ],
), ),
_buildDashboard(), _buildDashboard(cs),
], ],
), ),
); );
} }
Widget _buildDashboard() { Widget _buildDashboard(ColorScheme cs) {
return Positioned( return Positioned(
top: 100, top: 100,
right: 16, right: 16,
@@ -559,11 +559,11 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Container( child: Container(
width: 300, width: 300,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.15), color: cs.shadow.withValues(alpha: 0.15),
blurRadius: 30, blurRadius: 30,
spreadRadius: 5, spreadRadius: 5,
) )
@@ -573,15 +573,11 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Column( child: Column(
children: [ children: [
// Header
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [cs.onSurface, cs.onSurfaceVariant],
const Color(0xFF2C3E50),
const Color(0xFF34495E)
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
@@ -589,12 +585,11 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Icon(Icons.dashboard, Icon(Icons.dashboard, color: cs.onPrimary, size: 22),
color: Colors.white, size: 22), Text(
const Text(
"لوحة التحكم", "لوحة التحكم",
style: TextStyle( style: TextStyle(
color: Colors.white, color: cs.onPrimary,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -607,7 +602,6 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
// Mode Buttons
Row( Row(
children: [ children: [
Expanded( Expanded(
@@ -618,7 +612,8 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
setState(() => isLiveMode = false); setState(() => isLiveMode = false);
fetchData(); fetchData();
}, },
const Color(0xFF3498DB), cs.tertiary,
cs,
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
@@ -630,36 +625,35 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
setState(() => isLiveMode = true); setState(() => isLiveMode = true);
fetchData(); fetchData();
}, },
const Color(0xFF27AE60), const Color(0xFF10B981),
cs,
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Stats
_buildStatRow( _buildStatRow(
icon: Icons.live_tv, icon: Icons.live_tv,
label: "نشط الآن (مباشر)", label: "نشط الآن (مباشر)",
value: liveCount.toString(), value: liveCount.toString(),
color: const Color(0xFF27AE60), color: const Color(0xFF10B981),
cs: cs,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildStatRow( _buildStatRow(
icon: Icons.history, icon: Icons.history,
label: "إجمالي اليوم", label: "إجمالي اليوم",
value: dayCount.toString(), value: dayCount.toString(),
color: const Color(0xFF3498DB), color: cs.tertiary,
cs: cs,
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
// Last Update
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade50, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200), border: Border.all(color: cs.outline),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -670,7 +664,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
: "تحديث: $lastUpdated", : "تحديث: $lastUpdated",
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.grey.shade600, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -679,23 +673,24 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
? Icons.hourglass_bottom ? Icons.hourglass_bottom
: Icons.check_circle, : Icons.check_circle,
size: 14, size: 14,
color: isLoading ? Colors.orange : Colors.green, color: isLoading
? cs.tertiary
: const Color(0xFF10B981),
), ),
], ],
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Refresh Button
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: isLoading ? null : fetchData, onPressed: isLoading ? null : fetchData,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2C3E50), backgroundColor: cs.onSurface,
foregroundColor: Colors.white, foregroundColor: cs.onPrimary,
disabledBackgroundColor: Colors.grey.shade300, disabledBackgroundColor: cs.outline,
padding: const EdgeInsets.symmetric(vertical: 12), padding:
const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@@ -705,13 +700,14 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (isLoading) if (isLoading)
const SizedBox( SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>( valueColor:
Colors.white, AlwaysStoppedAnimation<Color>(
cs.onPrimary,
), ),
), ),
) )
@@ -746,6 +742,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
bool active, bool active,
VoidCallback onTap, VoidCallback onTap,
Color color, Color color,
ColorScheme cs,
) { ) {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
@@ -757,21 +754,21 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: active gradient: active
? LinearGradient( ? LinearGradient(
colors: [color, color.withOpacity(0.8)], colors: [color, color.withValues(alpha: 0.8)],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
) )
: null, : null,
color: active ? null : Colors.grey.shade100, color: active ? null : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(
color: active ? color : Colors.grey.shade300, color: active ? color : cs.outline,
width: 1.5, width: 1.5,
), ),
boxShadow: active boxShadow: active
? [ ? [
BoxShadow( BoxShadow(
color: color.withOpacity(0.3), color: color.withValues(alpha: 0.3),
blurRadius: 8, blurRadius: 8,
spreadRadius: 1, spreadRadius: 1,
) )
@@ -781,7 +778,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
child: Text( child: Text(
title, title,
style: TextStyle( style: TextStyle(
color: active ? Colors.white : Colors.grey.shade700, color: active ? cs.onPrimary : cs.onSurfaceVariant,
fontWeight: active ? FontWeight.bold : FontWeight.w600, fontWeight: active ? FontWeight.bold : FontWeight.w600,
fontSize: 13, fontSize: 13,
), ),
@@ -795,13 +792,14 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
required String label, required String label,
required String value, required String value,
required Color color, required Color color,
required ColorScheme cs,
}) { }) {
return Row( return Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.1), color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Icon(icon, color: color, size: 18), child: Icon(icon, color: color, size: 18),
@@ -815,7 +813,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
label, label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -4,14 +4,9 @@ import 'package:get/get.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:siro_admin/constant/links.dart'; import 'package:siro_admin/constant/links.dart';
// Keep your specific imports
import 'package:siro_admin/controller/functions/crud.dart'; import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.dart'; import 'package:siro_admin/views/widgets/snackbar.dart';
/// --------------------------------------------------------------------------
/// 1. DATA MODELS
/// --------------------------------------------------------------------------
class DriverLocation { class DriverLocation {
final double latitude; final double latitude;
final double longitude; final double longitude;
@@ -38,32 +33,21 @@ class DriverLocation {
} }
} }
/// --------------------------------------------------------------------------
/// 2. GETX CONTROLLER
/// --------------------------------------------------------------------------
/// تطبيع رقم الهاتف تلقائياً حسب الدولة
/// مثال: 0992952235 ← 963992952235 (سوريا)
/// مثال: 079XXXXXXX ← 96279XXXXXXX (أردن)
/// مثال: 010XXXXXXXX ← 2010XXXXXXXX (مصر)
String normalizePhone(String input) { String normalizePhone(String input) {
final clean = input.replaceAll(RegExp(r'\D+'), ''); final clean = input.replaceAll(RegExp(r'\D+'), '');
// Syria: 099XXXXXXX or 9639XXXXXXX
if (clean.length == 10 && clean.startsWith('09')) { if (clean.length == 10 && clean.startsWith('09')) {
return '963${clean.substring(1)}'; return '963${clean.substring(1)}';
} }
if (clean.length == 12 && clean.startsWith('963')) return clean; if (clean.length == 12 && clean.startsWith('963')) return clean;
if (clean.length == 9 && clean.startsWith('9')) return '963$clean'; if (clean.length == 9 && clean.startsWith('9')) return '963$clean';
// Jordan: 079XXXXXXX or 9627XXXXXXX
if (clean.length == 10 && clean.startsWith('07')) { if (clean.length == 10 && clean.startsWith('07')) {
return '962${clean.substring(1)}'; return '962${clean.substring(1)}';
} }
if (clean.length == 12 && clean.startsWith('962')) return clean; if (clean.length == 12 && clean.startsWith('962')) return clean;
if (clean.length == 9 && clean.startsWith('7')) return '962$clean'; if (clean.length == 9 && clean.startsWith('7')) return '962$clean';
// Egypt: 010XXXXXXXX or 2010XXXXXXXX
if (clean.length == 11 && clean.startsWith('01')) { if (clean.length == 11 && clean.startsWith('01')) {
return '20${clean.substring(1)}'; return '20${clean.substring(1)}';
} }
@@ -73,32 +57,26 @@ String normalizePhone(String input) {
} }
class RideMonitorController extends GetxController { class RideMonitorController extends GetxController {
// CONFIGURATION
final String apiUrl = "${AppLink.server}/Admin/rides/monitorRide.php"; final String apiUrl = "${AppLink.server}/Admin/rides/monitorRide.php";
// INPUT CONTROLLERS
final TextEditingController phoneInputController = TextEditingController(); final TextEditingController phoneInputController = TextEditingController();
// OBSERVABLES
var isTracking = false.obs; var isTracking = false.obs;
var isLoading = false.obs; var isLoading = false.obs;
var hasError = false.obs; var hasError = false.obs;
var errorMessage = ''.obs; var errorMessage = ''.obs;
// Driver & Ride Data
var driverLocation = Rxn<DriverLocation>(); var driverLocation = Rxn<DriverLocation>();
var driverName = "Unknown Driver".obs; var driverName = "Unknown Driver".obs;
var rideStatus = "Waiting...".obs; var rideStatus = "Waiting...".obs;
// Route Data
var startPoint = Rxn<LatLng>(); var startPoint = Rxn<LatLng>();
var endPoint = Rxn<LatLng>(); var endPoint = Rxn<LatLng>();
var routePolyline = <LatLng>[].obs; // List of points for the line var routePolyline = <LatLng>[].obs;
// Map Variables
final MapController mapController = MapController(); final MapController mapController = MapController();
Timer? _timer; Timer? _timer;
bool _isFirstLoad = true; // To trigger auto-fit bounds only on first success bool _isFirstLoad = true;
@override @override
void onClose() { void onClose() {
@@ -107,15 +85,12 @@ class RideMonitorController extends GetxController {
super.onClose(); super.onClose();
} }
// --- ACTIONS ---
void startSearch() { void startSearch() {
if (phoneInputController.text.trim().isEmpty) { if (phoneInputController.text.trim().isEmpty) {
mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً"); mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً");
return; return;
} }
// Reset state
hasError.value = false; hasError.value = false;
errorMessage.value = ''; errorMessage.value = '';
driverLocation.value = null; driverLocation.value = null;
@@ -126,14 +101,11 @@ class RideMonitorController extends GetxController {
rideStatus.value = "جاري التحميل..."; rideStatus.value = "جاري التحميل...";
_isFirstLoad = true; _isFirstLoad = true;
// Switch UI
isTracking.value = true; isTracking.value = true;
isLoading.value = true; isLoading.value = true;
// Start fetching
fetchRideData(); fetchRideData();
// Start Polling
_timer?.cancel(); _timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 10), (timer) { _timer = Timer.periodic(const Duration(seconds: 10), (timer) {
fetchRideData(); fetchRideData();
@@ -144,7 +116,6 @@ class RideMonitorController extends GetxController {
_timer?.cancel(); _timer?.cancel();
isTracking.value = false; isTracking.value = false;
isLoading.value = false; isLoading.value = false;
// phoneInputController.clear(); // اختياري: يمكنك إبقائه لتسهيل البحث مرة أخرى
} }
Future<void> fetchRideData() async { Future<void> fetchRideData() async {
@@ -152,7 +123,6 @@ class RideMonitorController extends GetxController {
if (phone.isEmpty) return; if (phone.isEmpty) return;
try { try {
// تطبيع رقم الهاتف تلقائياً حسب الدولة
String normalizedPhone = normalizePhone(phone); String normalizedPhone = normalizePhone(phone);
final response = await CRUD().post( final response = await CRUD().post(
link: apiUrl, link: apiUrl,
@@ -168,17 +138,14 @@ class RideMonitorController extends GetxController {
final data = final data =
jsonResponse['message'] ?? jsonResponse['data'] ?? jsonResponse; jsonResponse['message'] ?? jsonResponse['data'] ?? jsonResponse;
// 1. Parse Driver Info
if (data['driver_details'] != null) { if (data['driver_details'] != null) {
driverName.value = driverName.value =
data['driver_details']['fullname'] ?? "سائق غير معروف"; data['driver_details']['fullname'] ?? "سائق غير معروف";
} }
// 2. Parse Ride Info & Route
if (data['ride_details'] != null) { if (data['ride_details'] != null) {
rideStatus.value = data['ride_details']['status'] ?? "غير معروف"; rideStatus.value = data['ride_details']['status'] ?? "غير معروف";
// Parse Start/End Locations (Format: "lat,lng")
String? startStr = data['ride_details']['start_location']; String? startStr = data['ride_details']['start_location'];
String? endStr = data['ride_details']['end_location']; String? endStr = data['ride_details']['end_location'];
@@ -188,20 +155,17 @@ class RideMonitorController extends GetxController {
if (s != null && e != null) { if (s != null && e != null) {
startPoint.value = s; startPoint.value = s;
endPoint.value = e; endPoint.value = e;
routePolyline.value = [s, e]; // Straight line for now routePolyline.value = [s, e];
} }
} }
// 3. Parse Live Location
final locData = data['driver_location']; final locData = data['driver_location'];
if (locData is Map<String, dynamic>) { if (locData is Map<String, dynamic>) {
final newLocation = DriverLocation.fromJson(locData); final newLocation = DriverLocation.fromJson(locData);
driverLocation.value = newLocation; driverLocation.value = newLocation;
// 4. Update Camera Bounds
_updateMapBounds(); _updateMapBounds();
} else { } else {
// Even if no live driver, we might want to show the route
if (startPoint.value != null && endPoint.value != null) { if (startPoint.value != null && endPoint.value != null) {
_updateMapBounds(); _updateMapBounds();
} }
@@ -227,7 +191,6 @@ class RideMonitorController extends GetxController {
} }
} }
// Helper to parse "lat,lng" string
LatLng? _parseLatLngString(String? str) { LatLng? _parseLatLngString(String? str) {
if (str == null || !str.contains(',')) return null; if (str == null || !str.contains(',')) return null;
try { try {
@@ -240,7 +203,6 @@ class RideMonitorController extends GetxController {
} }
} }
// Logic to fit start, end, and driver on screen
void _updateMapBounds() { void _updateMapBounds() {
if (!_isFirstLoad) return; if (!_isFirstLoad) return;
@@ -264,48 +226,36 @@ class RideMonitorController extends GetxController {
); );
_isFirstLoad = false; _isFirstLoad = false;
} catch (e) { } catch (e) {
// Map Controller not ready yet
} }
} }
} }
} }
/// --------------------------------------------------------------------------
/// 3. UI SCREEN (Modern Light Theme)
/// --------------------------------------------------------------------------
class RideMonitorScreen extends StatelessWidget { class RideMonitorScreen extends StatelessWidget {
const RideMonitorScreen({super.key}); const RideMonitorScreen({super.key});
// 🎨 الألوان العصرية (Modern Palette)
final Color backgroundColor = const Color(0xFFF4F7FE);
final Color primaryColor = const Color(0xFF4318FF);
final Color textPrimary = const Color(0xFF2B3674);
final Color textSecondary = const Color(0xFFA3AED0);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final RideMonitorController controller = Get.put(RideMonitorController()); final RideMonitorController controller = Get.put(RideMonitorController());
return Scaffold( return Scaffold(
backgroundColor: backgroundColor, backgroundColor: cs.surface,
// الإبقاء على AppBar فقط في شاشة البحث
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight), preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(() { child: Obx(() {
if (controller.isTracking.value) { if (controller.isTracking.value) {
return const SizedBox return const SizedBox.shrink();
.shrink(); // إخفاء الـ AppBar في وضع التتبع للخريطة الكاملة
} }
return AppBar( return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: textPrimary), iconTheme: IconThemeData(color: cs.onSurface),
title: Text( title: Text(
"مراقبة الرحلات", "مراقبة الرحلات",
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,
), ),
@@ -315,18 +265,15 @@ class RideMonitorScreen extends StatelessWidget {
), ),
body: Obx(() { body: Obx(() {
if (!controller.isTracking.value) { if (!controller.isTracking.value) {
return _buildSearchForm(context, controller); return _buildSearchForm(context, controller, cs);
} }
return _buildMapTrackingView(context, controller); return _buildMapTrackingView(context, controller, cs);
}), }),
); );
} }
// ---------------------------------------------------------------------------
// واجهة البحث (Search View)
// ---------------------------------------------------------------------------
Widget _buildSearchForm( Widget _buildSearchForm(
BuildContext context, RideMonitorController controller) { BuildContext context, RideMonitorController controller, ColorScheme cs) {
return Center( return Center(
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
@@ -335,11 +282,11 @@ class RideMonitorScreen extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: primaryColor.withOpacity(0.08), color: cs.primary.withValues(alpha: 0.08),
blurRadius: 24, blurRadius: 24,
offset: const Offset(0, 10), offset: const Offset(0, 10),
) )
@@ -351,17 +298,16 @@ class RideMonitorScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor.withOpacity(0.1), color: cs.primary.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: child: Icon(Icons.radar_rounded, size: 60, color: cs.primary),
Icon(Icons.radar_rounded, size: 60, color: primaryColor),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
"تتبع رحلة نشطة", "تتبع رحلة نشطة",
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -370,7 +316,7 @@ class RideMonitorScreen extends StatelessWidget {
Text( Text(
"أدخل رقم هاتف السائق أو الراكب للبدء", "أدخل رقم هاتف السائق أو الراكب للبدء",
style: TextStyle( style: TextStyle(
color: textSecondary, color: cs.onSurfaceVariant,
fontSize: 14, fontSize: 14,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -378,27 +324,27 @@ class RideMonitorScreen extends StatelessWidget {
const SizedBox(height: 32), const SizedBox(height: 32),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: cs.surface,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white, width: 2), border: Border.all(color: cs.outline, width: 2),
), ),
child: TextField( child: TextField(
controller: controller.phoneInputController, controller: controller.phoneInputController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: "مثال: 0992952235...", hintText: "مثال: 0992952235...",
hintStyle: TextStyle(color: textSecondary), hintStyle: TextStyle(color: cs.onSurfaceVariant),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
vertical: 18, horizontal: 20), vertical: 18, horizontal: 20),
prefixIcon: prefixIcon:
Icon(Icons.phone_rounded, color: primaryColor), Icon(Icons.phone_rounded, color: cs.primary),
), ),
), ),
), ),
@@ -409,16 +355,16 @@ class RideMonitorScreen extends StatelessWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: controller.startSearch, onPressed: controller.startSearch,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: primaryColor, backgroundColor: cs.primary,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
), ),
child: const Text( child: Text(
"بدء المراقبة", "بدء المراقبة",
style: TextStyle( style: TextStyle(
color: Colors.white, color: cs.onPrimary,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -433,11 +379,8 @@ class RideMonitorScreen extends StatelessWidget {
); );
} }
// ---------------------------------------------------------------------------
// واجهة الخريطة (Map View)
// ---------------------------------------------------------------------------
Widget _buildMapTrackingView( Widget _buildMapTrackingView(
BuildContext context, RideMonitorController controller) { BuildContext context, RideMonitorController controller, ColorScheme cs) {
return Stack( return Stack(
children: [ children: [
FlutterMap( FlutterMap(
@@ -451,45 +394,36 @@ class RideMonitorScreen extends StatelessWidget {
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.siromove.admin', userAgentPackageName: 'com.siromove.admin',
), ),
// 1. ROUTE LINE (Polyline)
if (controller.routePolyline.isNotEmpty) if (controller.routePolyline.isNotEmpty)
PolylineLayer( PolylineLayer(
polylines: [ polylines: [
Polyline( Polyline(
points: controller.routePolyline, points: controller.routePolyline,
strokeWidth: 6.0, strokeWidth: 6.0,
color: primaryColor.withOpacity(0.9), color: cs.primary.withValues(alpha: 0.9),
borderStrokeWidth: 2.0, borderStrokeWidth: 2.0,
borderColor: primaryColor.withOpacity(0.3), borderColor: cs.primary.withValues(alpha: 0.3),
strokeCap: StrokeCap.round, strokeCap: StrokeCap.round,
strokeJoin: StrokeJoin.round, strokeJoin: StrokeJoin.round,
), ),
], ],
), ),
// 2. START & END MARKERS
MarkerLayer( MarkerLayer(
markers: [ markers: [
// Start Point (Green Dot)
if (controller.startPoint.value != null) if (controller.startPoint.value != null)
Marker( Marker(
point: controller.startPoint.value!, point: controller.startPoint.value!,
width: 30, width: 30,
height: 30, height: 30,
child: _buildPointMarker(const Color(0xFF10B981)), child: _buildPointMarker(const Color(0xFF10B981), cs),
), ),
// End Point (Red Dot)
if (controller.endPoint.value != null) if (controller.endPoint.value != null)
Marker( Marker(
point: controller.endPoint.value!, point: controller.endPoint.value!,
width: 30, width: 30,
height: 30, height: 30,
child: _buildPointMarker(const Color(0xFFEF4444)), child: _buildPointMarker(const Color(0xFFEF4444), cs),
), ),
// Driver Car Marker
if (controller.driverLocation.value != null) if (controller.driverLocation.value != null)
Marker( Marker(
point: LatLng( point: LatLng(
@@ -507,11 +441,11 @@ class RideMonitorScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: cs.onSurface.withValues(alpha: 0.2),
blurRadius: 10, blurRadius: 10,
spreadRadius: 2, spreadRadius: 2,
) )
@@ -519,7 +453,7 @@ class RideMonitorScreen extends StatelessWidget {
), ),
child: Icon( child: Icon(
Icons.directions_car_rounded, Icons.directions_car_rounded,
color: primaryColor, color: cs.primary,
size: 28, size: 28,
), ),
), ),
@@ -528,13 +462,13 @@ class RideMonitorScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2), horizontal: 6, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: textPrimary, color: cs.onSurface,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
"${controller.driverLocation.value!.speed.toInt()} كم", "${controller.driverLocation.value!.speed.toInt()} كم",
style: const TextStyle( style: TextStyle(
color: Colors.white, color: cs.onPrimary,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -549,48 +483,44 @@ class RideMonitorScreen extends StatelessWidget {
), ),
], ],
), ),
// زر التراجع (إيقاف التتبع) أعلى الشاشة
Positioned( Positioned(
top: MediaQuery.of(context).padding.top + 10, top: MediaQuery.of(context).padding.top + 10,
right: 20, // أو left حسب لغة التطبيق right: 20,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: cs.onSurface.withValues(alpha: 0.1),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
) )
], ],
), ),
child: IconButton( child: IconButton(
icon: Icon(Icons.close_rounded, color: textPrimary, size: 24), icon: Icon(Icons.close_rounded, color: cs.onSurface, size: 24),
onPressed: controller.stopTracking, onPressed: controller.stopTracking,
tooltip: "إيقاف المراقبة", tooltip: "إيقاف المراقبة",
), ),
), ),
), ),
// LOADING OVERLAY (Smooth Frosted Glass like)
if (controller.isLoading.value && if (controller.isLoading.value &&
controller.driverLocation.value == null && controller.driverLocation.value == null &&
controller.startPoint.value == null) controller.startPoint.value == null)
Container( Container(
color: Colors.white.withOpacity(0.8), color: cs.surface.withValues(alpha: 0.8),
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
CircularProgressIndicator( CircularProgressIndicator(
color: primaryColor, strokeWidth: 3), color: cs.primary, strokeWidth: 3),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
"جاري تحديد الموقع...", "جاري تحديد الموقع...",
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
), ),
@@ -599,19 +529,17 @@ class RideMonitorScreen extends StatelessWidget {
), ),
), ),
), ),
// ERROR OVERLAY
if (controller.hasError.value) if (controller.hasError.value)
Center( Center(
child: Container( child: Container(
margin: const EdgeInsets.all(24), margin: const EdgeInsets.all(24),
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: cs.onSurface.withValues(alpha: 0.1),
blurRadius: 20, blurRadius: 20,
offset: const Offset(0, 10), offset: const Offset(0, 10),
) )
@@ -623,17 +551,17 @@ class RideMonitorScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1), color: cs.error.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon(Icons.error_outline_rounded, child: Icon(Icons.error_outline_rounded,
color: Colors.red, size: 40), color: cs.error, size: 40),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
"حدث خطأ", "حدث خطأ",
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -642,7 +570,7 @@ class RideMonitorScreen extends StatelessWidget {
Text( Text(
controller.errorMessage.value, controller.errorMessage.value,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: textSecondary, height: 1.5), style: TextStyle(color: cs.onSurfaceVariant, height: 1.5),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
SizedBox( SizedBox(
@@ -650,8 +578,8 @@ class RideMonitorScreen extends StatelessWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: controller.stopTracking, onPressed: controller.stopTracking,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor, backgroundColor: cs.surface,
foregroundColor: textPrimary, foregroundColor: cs.onSurface,
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -666,8 +594,6 @@ class RideMonitorScreen extends StatelessWidget {
), ),
), ),
), ),
// INFO CARD (Bottom Floating Card)
if (!controller.hasError.value && !controller.isLoading.value) if (!controller.hasError.value && !controller.isLoading.value)
Positioned( Positioned(
bottom: 30, bottom: 30,
@@ -675,11 +601,11 @@ class RideMonitorScreen extends StatelessWidget {
right: 20, right: 20,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.08), color: cs.onSurface.withValues(alpha: 0.08),
blurRadius: 24, blurRadius: 24,
offset: const Offset(0, 10), offset: const Offset(0, 10),
) )
@@ -696,11 +622,11 @@ class RideMonitorScreen extends StatelessWidget {
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor.withOpacity(0.1), color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
child: Icon(Icons.person_rounded, child: Icon(Icons.person_rounded,
color: primaryColor, size: 28), color: cs.primary, size: 28),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
@@ -710,7 +636,7 @@ class RideMonitorScreen extends StatelessWidget {
Text( Text(
controller.driverName.value, controller.driverName.value,
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -735,7 +661,7 @@ class RideMonitorScreen extends StatelessWidget {
Text( Text(
controller.rideStatus.value, controller.rideStatus.value,
style: TextStyle( style: TextStyle(
color: textSecondary, color: cs.onSurfaceVariant,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -759,17 +685,19 @@ class RideMonitorScreen extends StatelessWidget {
Icons.speed_rounded, Icons.speed_rounded,
"${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س", "${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س",
const Color(0xFF3B82F6), const Color(0xFF3B82F6),
cs,
), ),
Container( Container(
width: 1, width: 1,
height: 30, height: 30,
color: Colors.grey.withOpacity(0.2)), color: cs.outline.withValues(alpha: 0.2)),
_buildModernInfoBadge( _buildModernInfoBadge(
Icons.access_time_rounded, Icons.access_time_rounded,
controller.driverLocation.value!.updatedAt controller.driverLocation.value!.updatedAt
.split(' ') .split(' ')
.last, .last,
const Color(0xFF8B5CF6), const Color(0xFF8B5CF6),
cs,
), ),
], ],
) )
@@ -781,13 +709,13 @@ class RideMonitorScreen extends StatelessWidget {
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: primaryColor, strokeWidth: 2), color: cs.primary, strokeWidth: 2),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
"جاري الاتصال بالسائق...", "جاري الاتصال بالسائق...",
style: TextStyle( style: TextStyle(
color: primaryColor, color: cs.primary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 13, fontSize: 13,
), ),
@@ -803,12 +731,10 @@ class RideMonitorScreen extends StatelessWidget {
); );
} }
// --- Helper Widgets --- Widget _buildPointMarker(Color color, ColorScheme cs) {
Widget _buildPointMarker(Color color) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.3), color: color.withValues(alpha: 0.3),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center( child: Center(
@@ -818,10 +744,10 @@ class RideMonitorScreen extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color, color: color,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2), border: Border.all(color: cs.surface, width: 2),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: color.withOpacity(0.5), color: color.withValues(alpha: 0.5),
blurRadius: 6, blurRadius: 6,
spreadRadius: 1, spreadRadius: 1,
) )
@@ -832,13 +758,14 @@ class RideMonitorScreen extends StatelessWidget {
); );
} }
Widget _buildModernInfoBadge(IconData icon, String text, Color iconColor) { Widget _buildModernInfoBadge(
IconData icon, String text, Color iconColor, ColorScheme cs) {
return Row( return Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: iconColor.withOpacity(0.1), color: iconColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Icon(icon, size: 16, color: iconColor), child: Icon(icon, size: 16, color: iconColor),
@@ -847,11 +774,11 @@ class RideMonitorScreen extends StatelessWidget {
Text( Text(
text, text,
style: TextStyle( style: TextStyle(
color: textPrimary, color: cs.onSurface,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
textDirection: TextDirection.ltr, // للحفاظ على اتجاه الأرقام textDirection: TextDirection.ltr,
), ),
], ],
); );
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/employee_controller/employee_controller.dart'; import 'package:siro_admin/controller/employee_controller/employee_controller.dart';
import 'package:siro_admin/controller/functions/upload_image copy.dart'; // تأكد من مسار الملف الصحيح import 'package:siro_admin/controller/functions/upload_image copy.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class EmployeePage extends StatelessWidget { class EmployeePage extends StatelessWidget {
@@ -10,27 +10,21 @@ class EmployeePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// حقن الكنترولر
Get.put(EmployeeController()); Get.put(EmployeeController());
final cs = Theme.of(context).colorScheme;
// ألوان الثيم
const Color bgColor = Color(0xFF0A0E27);
const Color cardColor = Color(0xFF1A1F3A);
const Color primaryAccent = Color(0xFF6366F1);
return Scaffold( return Scaffold(
backgroundColor: bgColor, backgroundColor: cs.surface,
body: GetBuilder<EmployeeController>( body: GetBuilder<EmployeeController>(
builder: (controller) { builder: (controller) {
return CustomScrollView( return CustomScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
slivers: [ slivers: [
// 1. App Bar
SliverAppBar( SliverAppBar(
expandedHeight: 100, expandedHeight: 100,
floating: true, floating: true,
pinned: true, pinned: true,
backgroundColor: bgColor, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
titlePadding: const EdgeInsets.only(bottom: 16), titlePadding: const EdgeInsets.only(bottom: 16),
@@ -40,19 +34,19 @@ class EmployeePage extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryAccent.withOpacity(0.2), color: cs.primary.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Icon(Icons.badge_rounded, child: Icon(Icons.badge_rounded,
color: Colors.white, size: 18), color: cs.onSurface, size: 18),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( Text(
'الموظفون', 'الموظفون',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
color: Colors.white, color: cs.onSurface,
fontFamily: 'Segoe UI', fontFamily: 'Segoe UI',
), ),
), ),
@@ -65,8 +59,8 @@ class EmployeePage extends StatelessWidget {
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
primaryAccent.withOpacity(0.15), cs.primary.withValues(alpha: 0.15),
bgColor, cs.surface,
], ],
), ),
), ),
@@ -74,18 +68,17 @@ class EmployeePage extends StatelessWidget {
), ),
), ),
// 2. قائمة الموظفين
if (controller.employee.isEmpty) if (controller.employee.isEmpty)
const SliverFillRemaining( SliverFillRemaining(
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.people_outline, Icon(Icons.people_outline,
size: 60, color: Colors.white24), size: 60, color: cs.onSurface.withValues(alpha: 0.24)),
SizedBox(height: 16), const SizedBox(height: 16),
Text("لا يوجد موظفين حالياً", Text("لا يوجد موظفين حالياً",
style: TextStyle(color: Colors.white54)), style: TextStyle(color: cs.onSurfaceVariant)),
], ],
), ),
), ),
@@ -100,8 +93,7 @@ class EmployeePage extends StatelessWidget {
return _EmployeeCard( return _EmployeeCard(
employee: employee, employee: employee,
index: index, index: index,
cardColor: cardColor, cs: cs,
primaryAccent: primaryAccent,
); );
}, },
childCount: controller.employee.length, childCount: controller.employee.length,
@@ -118,25 +110,22 @@ class EmployeePage extends StatelessWidget {
controller.id = controller.generateRandomId(8); controller.id = controller.generateRandomId(8);
Get.to(() => _EmployeeFormScreen(controller: controller)); Get.to(() => _EmployeeFormScreen(controller: controller));
}, },
backgroundColor: primaryAccent, backgroundColor: cs.primary,
child: const Icon(Icons.person_add_rounded, color: Colors.white), child: Icon(Icons.person_add_rounded, color: cs.onPrimary),
), ),
); );
} }
} }
// === بطاقة الموظف (تصميم جديد ومحسن) ===
class _EmployeeCard extends StatelessWidget { class _EmployeeCard extends StatelessWidget {
final Map<String, dynamic> employee; final Map<String, dynamic> employee;
final int index; final int index;
final Color cardColor; final ColorScheme cs;
final Color primaryAccent;
const _EmployeeCard({ const _EmployeeCard({
required this.employee, required this.employee,
required this.index, required this.index,
required this.cardColor, required this.cs,
required this.primaryAccent,
}); });
@override @override
@@ -147,12 +136,12 @@ class _EmployeeCard extends StatelessWidget {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: cardColor, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.05)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: Colors.black.withValues(alpha: 0.2),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -168,28 +157,27 @@ class _EmployeeCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// الصف العلوي: الحالة + أيقونة
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05), color: cs.outline.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(Icons.person, child: Icon(Icons.person,
color: Colors.white.withOpacity(0.7), size: 20), color: cs.onSurface.withValues(alpha: 0.7), size: 20),
), ),
Flexible( Flexible(
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6), horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: statusColor.withOpacity(0.1), color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: border:
Border.all(color: statusColor.withOpacity(0.3)), Border.all(color: statusColor.withValues(alpha: 0.3)),
), ),
child: Text( child: Text(
employee['status'] ?? 'Unknown', employee['status'] ?? 'Unknown',
@@ -208,23 +196,21 @@ class _EmployeeCard extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
// الاسم في سطر كامل ومميز
Text( Text(
employee['name'] ?? 'Unknown', employee['name'] ?? 'Unknown',
style: const TextStyle( style: TextStyle(
color: Colors.white, color: cs.onSurface,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 1.3, height: 1.3,
), ),
), ),
const Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
child: Divider(height: 1, color: Colors.white10), child: Divider(height: 1, color: cs.outline.withValues(alpha: 0.1)),
), ),
// تفاصيل التعليم والهاتف والموقع (مع دعم تعدد الأسطر)
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@@ -233,23 +219,22 @@ class _EmployeeCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoRow(Icons.phone_iphone_rounded, _buildInfoRow(Icons.phone_iphone_rounded,
employee['phone'] ?? '', Colors.white54), employee['phone'] ?? '', cs.onSurfaceVariant, cs),
const SizedBox(height: 12), // مسافة أكبر بين العناصر const SizedBox(height: 12),
_buildInfoRow( _buildInfoRow(
Icons.school_outlined, Icons.school_outlined,
employee['education'] ?? 'غير محدد', employee['education'] ?? 'غير محدد',
primaryAccent), 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), employee['site'] ?? 'غير محدد', Colors.blueGrey, cs),
], ],
), ),
), ),
// زر الاتصال الجانبي
const SizedBox(width: 16), const SizedBox(width: 16),
Material( Material(
color: Colors.green.withOpacity(0.1), color: const Color(0xFF10B981).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: InkWell( child: InkWell(
onTap: () => onTap: () =>
@@ -258,7 +243,7 @@ class _EmployeeCard extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: const Icon(Icons.call, child: const Icon(Icons.call,
color: Colors.green, size: 24), color: Color(0xFF10B981), size: 24),
), ),
), ),
), ),
@@ -272,25 +257,23 @@ class _EmployeeCard extends StatelessWidget {
); );
} }
Widget _buildInfoRow(IconData icon, String text, Color iconColor) { Widget _buildInfoRow(IconData icon, String text, Color iconColor, ColorScheme cs) {
return Row( return Row(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start, // محاذاة الأيقونة مع بداية النص
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 2), // ضبط بسيط لموقع الأيقونة padding: const EdgeInsets.only(top: 2),
child: Icon(icon, size: 16, color: iconColor.withOpacity(0.8)), child: Icon(icon, size: 16, color: iconColor.withValues(alpha: 0.8)),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
text, text,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.7), color: cs.onSurface.withValues(alpha: 0.7),
fontSize: 13, fontSize: 13,
height: 1.5, // تباعد الأسطر لسهولة القراءة height: 1.5,
), ),
// تم إزالة maxLines و overflow للسماح بالنص بالنزول لأسطر متعددة
), ),
), ),
], ],
@@ -305,25 +288,23 @@ class _EmployeeCard extends StatelessWidget {
} }
} }
// === شاشة إضافة موظف ===
class _EmployeeFormScreen extends StatelessWidget { class _EmployeeFormScreen extends StatelessWidget {
final EmployeeController controller; final EmployeeController controller;
const _EmployeeFormScreen({required this.controller}); const _EmployeeFormScreen({required this.controller});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const Color bgColor = Color(0xFF0A0E27); final cs = Theme.of(context).colorScheme;
const Color inputColor = Color(0xFF1A1F3A);
return Scaffold( return Scaffold(
backgroundColor: bgColor, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text("إضافة موظف جديد", title: Text("إضافة موظف جديد",
style: TextStyle(color: Colors.white)), style: TextStyle(color: cs.onSurface)),
backgroundColor: bgColor, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: Icon(Icons.arrow_back, color: cs.onSurface),
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
), ),
@@ -339,6 +320,7 @@ class _EmployeeFormScreen extends StatelessWidget {
child: _UploadButton( child: _UploadButton(
title: "الهوية (أمام)", title: "الهوية (أمام)",
icon: Icons.credit_card, icon: Icons.credit_card,
cs: cs,
onPressed: () async { onPressed: () async {
await ImageController().choosImage(AppLink.uploadEgypt, await ImageController().choosImage(AppLink.uploadEgypt,
'idFrontEmployee', controller.id); 'idFrontEmployee', controller.id);
@@ -350,6 +332,7 @@ class _EmployeeFormScreen extends StatelessWidget {
child: _UploadButton( child: _UploadButton(
title: "الهوية (خلف)", title: "الهوية (خلف)",
icon: Icons.credit_card_outlined, icon: Icons.credit_card_outlined,
cs: cs,
onPressed: () async { onPressed: () async {
await ImageController().choosImage(AppLink.uploadEgypt, await ImageController().choosImage(AppLink.uploadEgypt,
'idbackEmployee', controller.id); 'idbackEmployee', controller.id);
@@ -360,20 +343,20 @@ class _EmployeeFormScreen extends StatelessWidget {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildModernTextField( _buildModernTextField(
controller.name, "الاسم الكامل", Icons.person, inputColor), controller.name, "الاسم الكامل", Icons.person, cs),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildModernTextField( _buildModernTextField(
controller.phone, "رقم الهاتف", Icons.phone, inputColor, controller.phone, "رقم الهاتف", Icons.phone, cs,
type: TextInputType.phone), type: TextInputType.phone),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildModernTextField(controller.education, "التعليم / الملاحظات", _buildModernTextField(controller.education, "التعليم / الملاحظات",
Icons.school, inputColor), Icons.school, cs),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildModernTextField(controller.site, "الموقع / العنوان", _buildModernTextField(controller.site, "الموقع / العنوان",
Icons.location_on, inputColor), Icons.location_on, cs),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildModernTextField(controller.status, "الحالة (مثال: ممتاز)", _buildModernTextField(controller.status, "الحالة (مثال: ممتاز)",
Icons.star, inputColor), Icons.star, cs),
const SizedBox(height: 32), const SizedBox(height: 32),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
@@ -385,14 +368,14 @@ class _EmployeeFormScreen extends StatelessWidget {
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6366F1), backgroundColor: cs.primary,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
), ),
child: const Text("حفظ البيانات", child: Text("حفظ البيانات",
style: TextStyle( style: TextStyle(
color: Colors.white, color: cs.onPrimary,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
), ),
@@ -405,23 +388,23 @@ class _EmployeeFormScreen extends StatelessWidget {
} }
Widget _buildModernTextField(TextEditingController controller, String hint, Widget _buildModernTextField(TextEditingController controller, String hint,
IconData icon, Color fillColor, IconData icon, ColorScheme cs,
{TextInputType type = TextInputType.text}) { {TextInputType type = TextInputType.text}) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: fillColor, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.1)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
), ),
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
keyboardType: type, keyboardType: type,
style: const TextStyle(color: Colors.white), style: TextStyle(color: cs.onSurface),
maxLines: null, // السماح بتعدد الأسطر عند الإدخال أيضاً maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
labelText: hint, labelText: hint,
labelStyle: TextStyle(color: Colors.white.withOpacity(0.5)), labelStyle: TextStyle(color: cs.onSurface.withValues(alpha: 0.5)),
prefixIcon: Icon(icon, color: Colors.white38, size: 20), prefixIcon: Icon(icon, color: cs.onSurfaceVariant, size: 20),
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14), const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
@@ -437,9 +420,10 @@ class _UploadButton extends StatelessWidget {
final String title; final String title;
final IconData icon; final IconData icon;
final VoidCallback onPressed; final VoidCallback onPressed;
final ColorScheme cs;
const _UploadButton( const _UploadButton(
{required this.title, required this.icon, required this.onPressed}); {required this.title, required this.icon, required this.onPressed, required this.cs});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -449,21 +433,21 @@ class _UploadButton extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1A1F3A), color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: const Color(0xFF6366F1).withOpacity(0.3), color: cs.primary.withValues(alpha: 0.3),
style: BorderStyle.solid), style: BorderStyle.solid),
), ),
child: Column( child: Column(
children: [ children: [
Icon(icon, color: const Color(0xFF6366F1), size: 30), Icon(icon, color: cs.primary, size: 30),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(title, Text(title,
style: const TextStyle(color: Colors.white70, fontSize: 12)), style: TextStyle(color: cs.onSurface.withValues(alpha: 0.7), fontSize: 12)),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text("اضغط للرفع", Text("اضغط للرفع",
style: TextStyle(color: Colors.white38, fontSize: 10)), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)),
], ],
), ),
), ),
@@ -471,22 +455,21 @@ class _UploadButton extends StatelessWidget {
} }
} }
// === شاشة التفاصيل ===
class EmployeeDetails extends StatelessWidget { class EmployeeDetails extends StatelessWidget {
final int index; final int index;
const EmployeeDetails({super.key, required this.index}); const EmployeeDetails({super.key, required this.index});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const Color bgColor = Color(0xFF0A0E27); final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: bgColor, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: title:
const Text('تفاصيل الموظف', style: TextStyle(color: Colors.white)), Text('تفاصيل الموظف', style: TextStyle(color: cs.onSurface)),
backgroundColor: bgColor, backgroundColor: cs.surface,
iconTheme: const IconThemeData(color: Colors.white), iconTheme: IconThemeData(color: cs.onSurface),
elevation: 0, elevation: 0,
), ),
body: GetBuilder<EmployeeController>( body: GetBuilder<EmployeeController>(
@@ -497,24 +480,26 @@ class EmployeeDetails extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text("الهوية الأمامية", Text("الهوية الأمامية",
style: TextStyle( style: TextStyle(
color: Colors.white, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
const SizedBox(height: 10), const SizedBox(height: 10),
_buildImageViewer( _buildImageViewer(
'${AppLink.server}/card_image/idFrontEmployee-$employeeId.jpg', '${AppLink.server}/card_image/idFrontEmployee-$employeeId.jpg',
cs,
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
const Text("الهوية الخلفية", Text("الهوية الخلفية",
style: TextStyle( style: TextStyle(
color: Colors.white, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
const SizedBox(height: 10), const SizedBox(height: 10),
_buildImageViewer( _buildImageViewer(
'${AppLink.server}/card_image/idbackEmployee-$employeeId.jpg', '${AppLink.server}/card_image/idbackEmployee-$employeeId.jpg',
cs,
), ),
], ],
), ),
@@ -524,14 +509,14 @@ class EmployeeDetails extends StatelessWidget {
); );
} }
Widget _buildImageViewer(String url) { Widget _buildImageViewer(String url, ColorScheme cs) {
return Container( return Container(
width: double.infinity, width: double.infinity,
height: 220, height: 220,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1A1F3A), color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.1)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -539,23 +524,23 @@ class EmployeeDetails extends StatelessWidget {
url, url,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return const Center( return Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.broken_image_rounded, Icon(Icons.broken_image_rounded,
color: Colors.white24, size: 50), color: cs.onSurface.withValues(alpha: 0.24), size: 50),
SizedBox(height: 8), const SizedBox(height: 8),
Text("فشل تحميل الصورة", Text("فشل تحميل الصورة",
style: TextStyle(color: Colors.white24)), style: TextStyle(color: cs.onSurface.withValues(alpha: 0.24))),
], ],
), ),
); );
}, },
loadingBuilder: (context, child, loadingProgress) { loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child; if (loadingProgress == null) return child;
return const Center( return Center(
child: CircularProgressIndicator(color: Color(0xFF6366F1))); child: CircularProgressIndicator(color: cs.primary));
}, },
), ),
), ),
@@ -126,47 +126,51 @@ class _ErrorListPageState extends State<ErrorListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF0F4F8), backgroundColor: cs.surface,
appBar: _buildAppBar(), appBar: _buildAppBar(cs),
body: Column( body: Column(
children: [ children: [
_SearchBar( _SearchBar(
controller: _phoneCtrl, controller: _phoneCtrl,
onSearch: _searchByPhone, onSearch: _searchByPhone,
onClear: _clearSearch, onClear: _clearSearch,
cs: cs,
), ),
Expanded( Expanded(
child: _buildBody(), child: _buildBody(cs),
), ),
], ],
), ),
); );
} }
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar(ColorScheme cs) {
return AppBar( return AppBar(
elevation: 0, elevation: 0,
backgroundColor: const Color(0xFF0F172A), backgroundColor: cs.surfaceContainerHighest,
foregroundColor: Colors.white, foregroundColor: cs.onSurface,
centerTitle: true, centerTitle: true,
title: Container( title: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: cs.onSurface.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.2)), border: Border.all(color: cs.onSurface.withValues(alpha: 0.2)),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.error_outline, color: Colors.red, size: 22), Icon(Icons.error_outline, color: cs.error, size: 22),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
"سجل الأخطاء", "سجل الأخطاء",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: cs.onSurface,
), ),
), ),
], ],
@@ -175,11 +179,11 @@ class _ErrorListPageState extends State<ErrorListPage> {
); );
} }
Widget _buildBody() { Widget _buildBody(ColorScheme cs) {
if (_loading) { if (_loading) {
return const Center( return Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF0F172A)), valueColor: AlwaysStoppedAnimation<Color>(cs.primary),
), ),
); );
} }
@@ -189,13 +193,13 @@ class _ErrorListPageState extends State<ErrorListPage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.error_outline, size: 64, color: Colors.red.shade300), Icon(Icons.error_outline, size: 64, color: cs.error),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
_errorMsg!, _errorMsg!,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.red.shade700, color: cs.error,
fontSize: 14, fontSize: 14,
), ),
), ),
@@ -209,15 +213,15 @@ class _ErrorListPageState extends State<ErrorListPage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( const Icon(
Icons.check_circle_outline, Icons.check_circle_outline,
size: 64, size: 64,
color: Colors.green.shade300, color: Color(0xFF10B981),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'لا توجد سجلات أخطاء', 'لا توجد سجلات أخطاء',
style: TextStyle(fontSize: 16, color: Colors.grey), style: TextStyle(fontSize: 16, color: cs.onSurfaceVariant),
), ),
], ],
), ),
@@ -232,12 +236,12 @@ class _ErrorListPageState extends State<ErrorListPage> {
await _fetchLast20(); await _fetchLast20();
} }
}, },
color: const Color(0xFF0F172A), color: cs.primary,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
itemCount: _items.length, itemCount: _items.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return _ErrorTile(_items[index], index); return _ErrorTile(_items[index], index, cs);
}, },
), ),
); );
@@ -254,11 +258,13 @@ class _SearchBar extends StatefulWidget {
final TextEditingController controller; final TextEditingController controller;
final VoidCallback onSearch; final VoidCallback onSearch;
final VoidCallback onClear; final VoidCallback onClear;
final ColorScheme cs;
const _SearchBar({ const _SearchBar({
required this.controller, required this.controller,
required this.onSearch, required this.onSearch,
required this.onClear, required this.onClear,
required this.cs,
}); });
@override @override
@@ -270,18 +276,16 @@ class _SearchBarState extends State<_SearchBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = widget.cs;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: cs.surfaceContainerHighest,
colors: [Colors.white, Colors.grey.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.08), color: Colors.black.withValues(alpha: 0.08),
blurRadius: 16, blurRadius: 16,
spreadRadius: 2, spreadRadius: 2,
) )
@@ -303,15 +307,13 @@ class _SearchBarState extends State<_SearchBar> {
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
onSubmitted: (_) => widget.onSearch(), onSubmitted: (_) => widget.onSearch(),
style: const TextStyle(fontSize: 15), style: TextStyle(fontSize: 15, color: cs.onSurface),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'بحث برقم الهاتف', hintText: 'بحث برقم الهاتف',
hintStyle: TextStyle(color: Colors.grey.shade400), hintStyle: TextStyle(color: cs.onSurfaceVariant),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: _isFocused color: _isFocused ? cs.primary : cs.onSurfaceVariant,
? const Color(0xFF0F172A)
: Colors.grey.shade400,
), ),
suffixIcon: widget.controller.text.isNotEmpty suffixIcon: widget.controller.text.isNotEmpty
? InkWell( ? InkWell(
@@ -320,28 +322,28 @@ class _SearchBarState extends State<_SearchBar> {
setState(() {}); setState(() {});
}, },
child: Icon(Icons.close, child: Icon(Icons.close,
color: Colors.grey.shade400, size: 20), color: cs.onSurfaceVariant, size: 20),
) )
: null, : null,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: borderSide:
BorderSide(color: Colors.grey.shade300, width: 1), BorderSide(color: cs.outline, width: 1),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: borderSide:
BorderSide(color: Colors.grey.shade300, width: 1), BorderSide(color: cs.outline, width: 1),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide( borderSide: BorderSide(
color: Color(0xFF0F172A), color: cs.primary,
width: 2, width: 2,
), ),
), ),
filled: true, filled: true,
fillColor: Colors.white, fillColor: cs.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(vertical: 12), contentPadding: const EdgeInsets.symmetric(vertical: 12),
), ),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
@@ -352,8 +354,8 @@ class _SearchBarState extends State<_SearchBar> {
ElevatedButton( ElevatedButton(
onPressed: widget.onSearch, onPressed: widget.onSearch,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0F172A), backgroundColor: cs.primary,
foregroundColor: Colors.white, foregroundColor: cs.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -372,9 +374,9 @@ class _SearchBarState extends State<_SearchBar> {
OutlinedButton( OutlinedButton(
onPressed: widget.onClear, onPressed: widget.onClear,
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF0F172A), foregroundColor: cs.primary,
side: const BorderSide( side: BorderSide(
color: Color(0xFF0F172A), color: cs.primary,
width: 1.5, width: 1.5,
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -395,12 +397,12 @@ class _SearchBarState extends State<_SearchBar> {
class _ErrorTile extends StatelessWidget { class _ErrorTile extends StatelessWidget {
final ErrorLog item; final ErrorLog item;
final int index; final int index;
final ColorScheme cs;
const _ErrorTile(this.item, this.index); const _ErrorTile(this.item, this.index, this.cs);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// تحديد الألوان والأيقونات بناءً على نوع المستخدم
final isDriver = item.userType.toLowerCase().contains('driver') || final isDriver = item.userType.toLowerCase().contains('driver') ||
item.userType.toLowerCase().contains('سائق'); item.userType.toLowerCase().contains('سائق');
@@ -410,22 +412,18 @@ class _ErrorTile extends StatelessWidget {
final userTypeLabel = isDriver ? "سائق" : "راكب"; final userTypeLabel = isDriver ? "سائق" : "راكب";
final userTypeBgColor = isDriver final userTypeBgColor = isDriver
? const Color(0xFF10B981).withOpacity(0.1) ? const Color(0xFF10B981).withValues(alpha: 0.1)
: const Color(0xFFF59E0B).withOpacity(0.1); : const Color(0xFFF59E0B).withValues(alpha: 0.1);
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: cs.surfaceContainerHighest,
colors: [Colors.white, Colors.grey.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200, width: 1), border: Border.all(color: cs.outline, width: 1),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.06), color: Colors.black.withValues(alpha: 0.06),
blurRadius: 12, blurRadius: 12,
spreadRadius: 1, spreadRadius: 1,
) )
@@ -435,7 +433,6 @@ class _ErrorTile extends StatelessWidget {
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: Stack( child: Stack(
children: [ children: [
// خط علوي ملون
Positioned( Positioned(
top: 0, top: 0,
left: 0, left: 0,
@@ -446,7 +443,7 @@ class _ErrorTile extends StatelessWidget {
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
const Color(0xFFEF4444), const Color(0xFFEF4444),
Colors.red.shade400, cs.error,
], ],
begin: Alignment.centerLeft, begin: Alignment.centerLeft,
end: Alignment.centerRight, end: Alignment.centerRight,
@@ -459,7 +456,6 @@ class _ErrorTile extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
// الصف الأول: رقم الخطأ ونوع المستخدم
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -493,7 +489,7 @@ class _ErrorTile extends StatelessWidget {
'#${item.id}', '#${item.id}',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade500, color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -501,14 +497,13 @@ class _ErrorTile extends StatelessWidget {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// عنوان الخطأ (قابل للنسخ)
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.shade50, color: cs.error.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: Colors.red.shade200, color: cs.error.withValues(alpha: 0.3),
width: 1, width: 1,
), ),
), ),
@@ -516,7 +511,7 @@ class _ErrorTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(Icons.warning_amber_rounded, Icon(Icons.warning_amber_rounded,
size: 18, color: Colors.red.shade600), size: 18, color: cs.error),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: SelectableText( child: SelectableText(
@@ -524,7 +519,7 @@ class _ErrorTile extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.red.shade900, color: cs.error,
height: 1.4, height: 1.4,
), ),
), ),
@@ -534,15 +529,14 @@ class _ErrorTile extends StatelessWidget {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// التفاصيل (إن وجدت)
if (item.details.isNotEmpty) ...[ if (item.details.isNotEmpty) ...[
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade50, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: Colors.grey.shade200, color: cs.outline,
width: 1, width: 1,
), ),
), ),
@@ -550,14 +544,14 @@ class _ErrorTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(Icons.info_outline, Icon(Icons.info_outline,
size: 16, color: Colors.grey.shade600), size: 16, color: cs.onSurfaceVariant),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: SelectableText( child: SelectableText(
item.details, item.details,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade700, color: cs.onSurfaceVariant,
height: 1.4, height: 1.4,
), ),
), ),
@@ -568,7 +562,6 @@ class _ErrorTile extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
// معلومات تقنية
Wrap( Wrap(
spacing: 6, spacing: 6,
runSpacing: 6, runSpacing: 6,
@@ -578,25 +571,29 @@ class _ErrorTile extends StatelessWidget {
icon: Icons.phone, icon: Icons.phone,
label: 'الهاتف', label: 'الهاتف',
value: item.phone, value: item.phone,
color: Colors.blue, color: cs.primary,
cs: cs,
), ),
_buildInfoBadge( _buildInfoBadge(
icon: Icons.person_outline, icon: Icons.person_outline,
label: 'المعرف', label: 'المعرف',
value: item.userId, value: item.userId,
color: Colors.purple, color: Colors.purple,
cs: cs,
), ),
_buildInfoBadge( _buildInfoBadge(
icon: Icons.devices, icon: Icons.devices,
label: 'Path', label: 'Path',
value: item.device, value: item.device,
color: Colors.orange, color: Colors.orange,
cs: cs,
), ),
_buildInfoBadge( _buildInfoBadge(
icon: Icons.schedule, icon: Icons.schedule,
label: 'التاريخ', label: 'التاريخ',
value: item.createdAt, value: item.createdAt,
color: Colors.teal, color: Colors.teal,
cs: cs,
), ),
], ],
), ),
@@ -614,13 +611,14 @@ class _ErrorTile extends StatelessWidget {
required String label, required String label,
required String value, required String value,
required Color color, required Color color,
required ColorScheme cs,
}) { }) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.08), color: color.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3), width: 1), border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -635,7 +633,7 @@ class _ErrorTile extends StatelessWidget {
text: "$label: ", text: "$label: ",
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: color.withOpacity(0.7), color: color.withValues(alpha: 0.7),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/financial_v2_controller.dart'; import 'package:siro_admin/controller/admin/financial_v2_controller.dart';
class FinancialV2Page extends StatelessWidget { class FinancialV2Page extends StatelessWidget {
@@ -8,19 +7,21 @@ class FinancialV2Page extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final controller = Get.put(FinancialV2Controller()); final controller = Get.put(FinancialV2Controller());
return Scaffold( return Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text('الإدارة المالية المتقدمة', title: Text('الإدارة المالية المتقدمة',
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold, color: cs.onSurface)),
backgroundColor: AppColor.surface, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: cs.onSurface),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.refresh_rounded), icon: Icon(Icons.refresh_rounded, color: cs.onSurface),
onPressed: () => controller.fetchAllFinancials(), onPressed: () => controller.fetchAllFinancials(),
) )
], ],
@@ -28,8 +29,8 @@ class FinancialV2Page extends StatelessWidget {
body: GetBuilder<FinancialV2Controller>( body: GetBuilder<FinancialV2Controller>(
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));
} }
return SingleChildScrollView( return SingleChildScrollView(
@@ -37,13 +38,13 @@ class FinancialV2Page extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildMainFinancialStats(ctrl.stats), _buildMainFinancialStats(ctrl.stats, cs),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildSectionTitle('طرق الدفع'), _buildSectionTitle('طرق الدفع', cs),
_buildPaymentMethodBreakdown(ctrl.stats), _buildPaymentMethodBreakdown(ctrl.stats, cs),
const SizedBox(height: 32), const SizedBox(height: 32),
_buildSectionTitle('تسويات الكباتن (مستحقات معلقة)'), _buildSectionTitle('تسويات الكباتن (مستحقات معلقة)', cs),
_buildSettlementsList(ctrl.settlements), _buildSettlementsList(ctrl.settlements, cs),
const SizedBox(height: 40), const SizedBox(height: 40),
], ],
), ),
@@ -53,13 +54,13 @@ class FinancialV2Page extends StatelessWidget {
); );
} }
Widget _buildSectionTitle(String title) { Widget _buildSectionTitle(String title, ColorScheme cs) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 16),
child: Text( child: Text(
title, title,
style: const TextStyle( style: TextStyle(
color: AppColor.textPrimary, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -67,14 +68,15 @@ class FinancialV2Page extends StatelessWidget {
); );
} }
Widget _buildMainFinancialStats(Map<String, dynamic> stats) { Widget _buildMainFinancialStats(Map<String, dynamic> stats, ColorScheme cs) {
return Column( return Column(
children: [ children: [
_buildFinancialCard( _buildFinancialCard(
'إجمالي عمولة المنصة', 'إجمالي عمولة المنصة',
'${stats['total_platform_commission'] ?? 0} ج.م', '${stats['total_platform_commission'] ?? 0} ج.م',
Icons.account_balance_rounded, Icons.account_balance_rounded,
AppColor.accent, cs.primary,
cs,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
@@ -84,7 +86,8 @@ class FinancialV2Page extends StatelessWidget {
'إجمالي دخل الكباتن', 'إجمالي دخل الكباتن',
'${stats['total_driver_pay'] ?? 0}', '${stats['total_driver_pay'] ?? 0}',
Icons.person_pin_rounded, Icons.person_pin_rounded,
AppColor.info, cs.tertiary,
cs,
isSmall: true, isSmall: true,
), ),
), ),
@@ -94,7 +97,8 @@ class FinancialV2Page extends StatelessWidget {
'إجمالي الإيرادات', 'إجمالي الإيرادات',
'${stats['total_revenue'] ?? 0}', '${stats['total_revenue'] ?? 0}',
Icons.payments_rounded, Icons.payments_rounded,
AppColor.success, const Color(0xFF10B981),
cs,
isSmall: true, isSmall: true,
), ),
), ),
@@ -105,18 +109,18 @@ class FinancialV2Page extends StatelessWidget {
} }
Widget _buildFinancialCard( Widget _buildFinancialCard(
String title, String value, IconData icon, Color color, String title, String value, IconData icon, Color color, ColorScheme cs,
{bool isSmall = false}) { {bool isSmall = false}) {
return Container( return Container(
padding: EdgeInsets.all(isSmall ? 16 : 24), padding: EdgeInsets.all(isSmall ? 16 : 24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.2)), border: Border.all(color: color.withValues(alpha: 0.2)),
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [AppColor.surface, color.withOpacity(0.05)], colors: [cs.surfaceContainerHighest, color.withValues(alpha: 0.05)],
), ),
), ),
child: Row( child: Row(
@@ -124,7 +128,7 @@ class FinancialV2Page extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.1), color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
), ),
child: Icon(icon, color: color, size: isSmall ? 20 : 28), child: Icon(icon, color: color, size: isSmall ? 20 : 28),
@@ -135,12 +139,12 @@ class FinancialV2Page extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(title, Text(title,
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 12)), color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text(value, Text(value,
style: TextStyle( style: TextStyle(
color: AppColor.textPrimary, color: cs.onSurface,
fontSize: isSmall ? 18 : 24, fontSize: isSmall ? 18 : 24,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
], ],
@@ -151,7 +155,7 @@ class FinancialV2Page extends StatelessWidget {
); );
} }
Widget _buildPaymentMethodBreakdown(Map<String, dynamic> stats) { Widget _buildPaymentMethodBreakdown(Map<String, dynamic> stats, ColorScheme cs) {
double cash = double.tryParse(stats['cash_payments'].toString()) ?? 0; double cash = double.tryParse(stats['cash_payments'].toString()) ?? 0;
double digital = double.tryParse(stats['digital_payments'].toString()) ?? 0; double digital = double.tryParse(stats['digital_payments'].toString()) ?? 0;
double total = cash + digital; double total = cash + digital;
@@ -160,21 +164,21 @@ class FinancialV2Page extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Column( child: Column(
children: [ children: [
_buildPaymentBar('نقدي (Cash)', cash, total, AppColor.warning), _buildPaymentBar('نقدي (Cash)', cash, total, cs.tertiary, cs),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildPaymentBar('إلكتروني / محفظة', digital, total, AppColor.info), _buildPaymentBar('إلكتروني / محفظة', digital, total, cs.tertiary, cs),
], ],
), ),
); );
} }
Widget _buildPaymentBar( Widget _buildPaymentBar(
String label, double value, double total, Color color) { String label, double value, double total, Color color, ColorScheme cs) {
double percent = value / total; double percent = value / total;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -183,8 +187,7 @@ class FinancialV2Page extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(label, Text(label,
style: style: TextStyle(color: cs.onSurface, fontSize: 13)),
const TextStyle(color: AppColor.textPrimary, fontSize: 13)),
Text('${value.toStringAsFixed(0)} ج.م', Text('${value.toStringAsFixed(0)} ج.م',
style: TextStyle(color: color, fontWeight: FontWeight.bold)), style: TextStyle(color: color, fontWeight: FontWeight.bold)),
], ],
@@ -194,7 +197,7 @@ class FinancialV2Page extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: percent, value: percent,
backgroundColor: AppColor.divider, backgroundColor: cs.outline,
color: color, color: color,
minHeight: 8, minHeight: 8,
), ),
@@ -203,9 +206,11 @@ class FinancialV2Page extends StatelessWidget {
); );
} }
Widget _buildSettlementsList(List<dynamic> settlements) { Widget _buildSettlementsList(List<dynamic> settlements, ColorScheme cs) {
if (settlements.isEmpty) { if (settlements.isEmpty) {
return const Center(child: Text('لا توجد تسويات معلقة')); return Center(
child: Text('لا توجد تسويات معلقة',
style: TextStyle(color: cs.onSurfaceVariant)));
} }
return ListView.builder( return ListView.builder(
@@ -218,9 +223,9 @@ class FinancialV2Page extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: Row( child: Row(
children: [ children: [
@@ -229,36 +234,36 @@ class FinancialV2Page extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('${s['first_name']} ${s['last_name']}', Text('${s['first_name']} ${s['last_name']}',
style: const TextStyle( style: TextStyle(
color: AppColor.textPrimary, color: cs.onSurface,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
Text(s['phone'] ?? '', Text(s['phone'] ?? '',
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 12)), color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('${s['total_rides']} رحلة مكتملة', Text('${s['total_rides']} رحلة مكتملة',
style: const TextStyle( style: TextStyle(
color: AppColor.info, fontSize: 11)), color: cs.tertiary, fontSize: 11)),
], ],
), ),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
const Text('المستحقات', Text('المستحقات',
style: TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 10)), color: cs.onSurfaceVariant, fontSize: 10)),
Text('${s['total_earned']} ج.م', Text('${s['total_earned']} ج.م',
style: const TextStyle( style: TextStyle(
color: AppColor.accent, color: cs.primary,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
const SizedBox(height: 4), const SizedBox(height: 4),
ElevatedButton( ElevatedButton(
onPressed: () {}, onPressed: () {},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColor.accent.withOpacity(0.1), backgroundColor: cs.primary.withValues(alpha: 0.1),
foregroundColor: AppColor.accent, foregroundColor: cs.primary,
elevation: 0, elevation: 0,
minimumSize: const Size(80, 32), minimumSize: const Size(80, 32),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -15,16 +15,14 @@ class _HeatmapPageState extends State<HeatmapPage> {
List<CircleMarker> _markers = []; List<CircleMarker> _markers = [];
bool _isLoading = true; bool _isLoading = true;
// ─── فلاتر ─── String _selectedSource = 'all';
String _selectedSource = 'all'; // all | geofence | app_usage | silent_push String _selectedCountry = 'all';
String _selectedCountry = 'all'; // all | JO | SY | EG | IQ
int _daysFilter = 7; int _daysFilter = 7;
final MapController _mapController = MapController(); final MapController _mapController = MapController();
// مراكز كل دولة للانتقال السريع
final Map<String, LatLng> _countryCenters = { final Map<String, LatLng> _countryCenters = {
'all': const LatLng(31.9522, 35.9334), // عمان 'all': const LatLng(31.9522, 35.9334),
'JO': const LatLng(31.9522, 35.9334), 'JO': const LatLng(31.9522, 35.9334),
'SY': const LatLng(33.5138, 36.2765), 'SY': const LatLng(33.5138, 36.2765),
'EG': const LatLng(30.0444, 31.2357), 'EG': const LatLng(30.0444, 31.2357),
@@ -60,18 +58,17 @@ class _HeatmapPageState extends State<HeatmapPage> {
final lng = double.tryParse(point['longitude'].toString()) ?? 0.0; final lng = double.tryParse(point['longitude'].toString()) ?? 0.0;
final source = point['source'].toString(); final source = point['source'].toString();
// ألوان حسب المصدر
Color markerColor; Color markerColor;
switch (source) { switch (source) {
case 'geofence': case 'geofence':
markerColor = Colors.green.withOpacity(0.65); // 🟢 دخل منطقة markerColor = Colors.green.withValues(alpha: 0.65);
break; break;
case 'silent_push': case 'silent_push':
markerColor = Colors.orange.withOpacity(0.55); // 🟠 إيقاظ صامت markerColor = Colors.orange.withValues(alpha: 0.55);
break; break;
case 'app_usage': case 'app_usage':
default: default:
markerColor = Colors.blue.withOpacity(0.5); // 🔵 فتح عادي markerColor = Colors.blue.withValues(alpha: 0.5);
} }
return CircleMarker( return CircleMarker(
@@ -93,22 +90,23 @@ class _HeatmapPageState extends State<HeatmapPage> {
void _applyFilter() { void _applyFilter() {
_fetchHeatmapData(); _fetchHeatmapData();
// تحريك الخريطة لمركز الدولة المختارة
final center = _countryCenters[_selectedCountry] ?? _countryCenters['all']!; final center = _countryCenters[_selectedCountry] ?? _countryCenters['all']!;
_mapController.move(center, 11.0); _mapController.move(center, 11.0);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFF0F0F1A), backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text( title: const Text(
'خريطة النشاط الحرارية', 'خريطة النشاط الحرارية',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15), style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
), ),
backgroundColor: const Color(0xFF1A1A2E), backgroundColor: cs.surface,
foregroundColor: Colors.white, foregroundColor: cs.onSurface,
centerTitle: true, centerTitle: true,
actions: [ actions: [
IconButton( IconButton(
@@ -120,14 +118,13 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
body: Column( body: Column(
children: [ children: [
// ─── شريط الفلاتر ───
Container( Container(
color: const Color(0xFF1A1A2E), color: cs.surfaceContainerHighest,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column( child: Column(
children: [ children: [
// فلتر المصدر
_buildFilterRow( _buildFilterRow(
cs: cs,
label: 'المصدر:', label: 'المصدر:',
options: const { options: const {
'all': 'الكل', 'all': 'الكل',
@@ -139,8 +136,8 @@ class _HeatmapPageState extends State<HeatmapPage> {
onChanged: (v) => setState(() => _selectedSource = v), onChanged: (v) => setState(() => _selectedSource = v),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
// فلتر الدولة
_buildFilterRow( _buildFilterRow(
cs: cs,
label: 'الدولة:', label: 'الدولة:',
options: const { options: const {
'all': 'الكل', 'all': 'الكل',
@@ -153,10 +150,9 @@ class _HeatmapPageState extends State<HeatmapPage> {
onChanged: (v) => setState(() => _selectedCountry = v), onChanged: (v) => setState(() => _selectedCountry = v),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
// فلتر الفترة الزمنية + زر تطبيق
Row( Row(
children: [ children: [
const Text('الفترة:', style: TextStyle(color: Colors.white54, fontSize: 11, fontWeight: FontWeight.bold)), Text('الفترة:', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.bold)),
const SizedBox(width: 8), const SizedBox(width: 8),
...[1, 7, 30].map((days) => Padding( ...[1, 7, 30].map((days) => Padding(
padding: const EdgeInsets.only(right: 6), padding: const EdgeInsets.only(right: 6),
@@ -166,19 +162,19 @@ class _HeatmapPageState extends State<HeatmapPage> {
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _daysFilter == days color: _daysFilter == days
? const Color(0xFF6366F1) ? cs.primary
: const Color(0xFF2D2D42), : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: _daysFilter == days color: _daysFilter == days
? const Color(0xFF818CF8) ? cs.primary
: Colors.transparent, : Colors.transparent,
), ),
), ),
child: Text( child: Text(
'$days يوم', '$days يوم',
style: TextStyle( style: TextStyle(
color: _daysFilter == days ? Colors.white : Colors.white54, color: _daysFilter == days ? cs.onSurface : cs.onSurfaceVariant,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -192,8 +188,8 @@ class _HeatmapPageState extends State<HeatmapPage> {
icon: const Icon(Icons.filter_alt_rounded, size: 14), icon: const Icon(Icons.filter_alt_rounded, size: 14),
label: const Text('تطبيق', style: TextStyle(fontSize: 11)), label: const Text('تطبيق', style: TextStyle(fontSize: 11)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6366F1), backgroundColor: cs.primary,
foregroundColor: Colors.white, foregroundColor: cs.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
), ),
@@ -204,18 +200,17 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
), ),
// ─── الخريطة ───
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [
_isLoading _isLoading
? const Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(color: Color(0xFF6366F1)), CircularProgressIndicator(color: cs.primary),
SizedBox(height: 12), const SizedBox(height: 12),
Text('جاري تحميل البيانات...', style: TextStyle(color: Colors.white54, fontSize: 12)), Text('جاري تحميل البيانات...', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)),
], ],
), ),
) )
@@ -234,44 +229,42 @@ class _HeatmapPageState extends State<HeatmapPage> {
], ],
), ),
// ─── مفتاح الألوان ───
Positioned( Positioned(
bottom: 12, bottom: 12,
right: 12, right: 12,
child: Container( child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1A1A2E).withOpacity(0.9), color: cs.surfaceContainerHighest.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white12), border: Border.all(color: cs.outline.withValues(alpha: 0.3)),
), ),
child: const Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_LegendItem(color: Colors.green, label: 'دخل منطقة سياج'), _LegendItem(color: Colors.green, label: 'دخل منطقة سياج', cs: cs),
SizedBox(height: 4), const SizedBox(height: 4),
_LegendItem(color: Colors.blue, label: 'فتح عادي للتطبيق'), _LegendItem(color: Colors.blue, label: 'فتح عادي للتطبيق', cs: cs),
SizedBox(height: 4), const SizedBox(height: 4),
_LegendItem(color: Colors.orange, label: 'إيقاظ صامت'), _LegendItem(color: Colors.orange, label: 'إيقاظ صامت', cs: cs),
], ],
), ),
), ),
), ),
// ─── عدد النقاط ───
Positioned( Positioned(
top: 12, top: 12,
left: 12, left: 12,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1A1A2E).withOpacity(0.9), color: cs.surfaceContainerHighest.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white12), border: Border.all(color: cs.outline.withValues(alpha: 0.3)),
), ),
child: Text( child: Text(
'${_markers.length} نقطة', '${_markers.length} نقطة',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold), style: TextStyle(color: cs.onSurface, fontSize: 11, fontWeight: FontWeight.bold),
), ),
), ),
), ),
@@ -284,6 +277,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
} }
Widget _buildFilterRow({ Widget _buildFilterRow({
required ColorScheme cs,
required String label, required String label,
required Map<String, String> options, required Map<String, String> options,
required String selected, required String selected,
@@ -291,7 +285,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
}) { }) {
return Row( return Row(
children: [ children: [
Text(label, style: const TextStyle(color: Colors.white54, fontSize: 11, fontWeight: FontWeight.bold)), Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.bold)),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -306,16 +300,16 @@ class _HeatmapPageState extends State<HeatmapPage> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? const Color(0xFF6366F1) : const Color(0xFF2D2D42), color: isSelected ? cs.primary : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: isSelected ? const Color(0xFF818CF8) : Colors.transparent, color: isSelected ? cs.primary : Colors.transparent,
), ),
), ),
child: Text( child: Text(
e.value, e.value,
style: TextStyle( style: TextStyle(
color: isSelected ? Colors.white : Colors.white54, color: isSelected ? cs.onSurface : cs.onSurfaceVariant,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -335,7 +329,8 @@ class _HeatmapPageState extends State<HeatmapPage> {
class _LegendItem extends StatelessWidget { class _LegendItem extends StatelessWidget {
final Color color; final Color color;
final String label; final String label;
const _LegendItem({required this.color, required this.label}); final ColorScheme cs;
const _LegendItem({required this.color, required this.label, required this.cs});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -344,7 +339,7 @@ class _LegendItem extends StatelessWidget {
children: [ children: [
Container(width: 10, height: 10, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), Container(width: 10, height: 10, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(label, style: const TextStyle(color: Colors.white70, fontSize: 10)), Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)),
], ],
); );
} }
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
class SocialIntelligenceController extends GetxController { class SocialIntelligenceController extends GetxController {
@@ -42,36 +41,38 @@ class SocialIntelligenceScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final controller = Get.put(SocialIntelligenceController()); final controller = Get.put(SocialIntelligenceController());
return Scaffold( return Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text( title: Text(
'استخبارات السوشيال ميديا (Social Intelligence)', 'استخبارات السوشيال ميديا (Social Intelligence)',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16), style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: cs.onSurface),
), ),
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
foregroundColor: AppColor.textPrimary, foregroundColor: cs.onSurface,
iconTheme: IconThemeData(color: cs.onSurface),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: Icon(Icons.refresh, color: cs.onSurface),
onPressed: () => controller.fetchReports(), onPressed: () => controller.fetchReports(),
) )
], ],
), ),
body: Obx(() { body: Obx(() {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return const Center( return Center(
child: CircularProgressIndicator(color: AppColor.accent)); child: CircularProgressIndicator(color: cs.primary));
} }
if (controller.reports.isEmpty) { if (controller.reports.isEmpty) {
return const Center( return Center(
child: Text( child: Text(
'لا توجد تقارير حالياً', 'لا توجد تقارير حالياً',
style: TextStyle(color: AppColor.textSecondary), style: TextStyle(color: cs.onSurfaceVariant),
), ),
); );
} }
@@ -84,45 +85,45 @@ class SocialIntelligenceScreen extends StatelessWidget {
final isWeekly = report['is_weekly'].toString() == '1'; final isWeekly = report['is_weekly'].toString() == '1';
return Card( return Card(
color: AppColor.surface, color: cs.surfaceContainerHighest,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: BorderSide( side: BorderSide(
color: isWeekly ? AppColor.accent : AppColor.divider, color: isWeekly ? cs.primary : cs.outline,
width: isWeekly ? 1.5 : 1.0), width: isWeekly ? 1.5 : 1.0),
), ),
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
child: ExpansionTile( child: ExpansionTile(
iconColor: AppColor.accent, iconColor: cs.primary,
collapsedIconColor: AppColor.textSecondary, collapsedIconColor: cs.onSurfaceVariant,
title: Text( title: Text(
isWeekly isWeekly
? '📈 تقرير السوق الأسبوعي الشامل' ? '📈 تقرير السوق الأسبوعي الشامل'
: '📊 تقرير ${report['platform'].toString().toUpperCase()}', : '📊 تقرير ${report['platform'].toString().toUpperCase()}',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isWeekly ? AppColor.accent : AppColor.textPrimary, color: isWeekly ? cs.primary : cs.onSurface,
fontSize: 14, fontSize: 14,
), ),
), ),
subtitle: Text( subtitle: Text(
report['created_at'].toString(), report['created_at'].toString(),
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 11), color: cs.onSurfaceVariant, fontSize: 11),
), ),
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
width: double.infinity, width: double.infinity,
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border(top: BorderSide(color: AppColor.divider)), border: Border(top: BorderSide(color: cs.outline)),
), ),
child: Directionality( child: Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: HtmlWidget( child: HtmlWidget(
report['report_html'] ?? '', report['report_html'] ?? '',
textStyle: const TextStyle( textStyle: TextStyle(
color: AppColor.textPrimary, color: cs.onSurface,
fontSize: 13, fontSize: 13,
height: 1.6), height: 1.6),
), ),
+89 -126
View File
@@ -7,19 +7,6 @@ import 'package:siro_admin/views/widgets/snackbar.dart';
import '../../print.dart'; import '../../print.dart';
// ══════════════════════════════════════════════════════════════
// DESIGN TOKENS (same as AdminHomePage)
// ══════════════════════════════════════════════════════════════
const Color _bg = Color(0xFF0D1117);
const Color _surface = Color(0xFF161B22);
const Color _surfaceElevated = Color(0xFF1C2333);
const Color _accent = Color(0xFF00D4AA);
const Color _warning = Color(0xFFFFCB6B);
const Color _info = Color(0xFF82AAFF);
const Color _textPrimary = Color(0xFFE6EDF3);
const Color _textSecondary = Color(0xFF7D8590);
const Color _divider = Color(0xFF21262D);
class PackageUpdateScreen extends StatelessWidget { class PackageUpdateScreen extends StatelessWidget {
PackageUpdateScreen({super.key}); PackageUpdateScreen({super.key});
@@ -27,19 +14,20 @@ class PackageUpdateScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: _bg, backgroundColor: cs.surface,
appBar: _buildAppBar(), appBar: _buildAppBar(cs),
body: GetBuilder<PackageController>( body: GetBuilder<PackageController>(
builder: (controller) { builder: (controller) {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return const Center( return Center(
child: CircularProgressIndicator(color: _accent, strokeWidth: 2), child: CircularProgressIndicator(color: cs.primary, strokeWidth: 2),
); );
} }
if (controller.packages.isEmpty) { if (controller.packages.isEmpty) {
return _buildEmptyState(); return _buildEmptyState(cs);
} }
return ListView.separated( return ListView.separated(
@@ -48,7 +36,7 @@ class PackageUpdateScreen extends StatelessWidget {
separatorBuilder: (_, __) => const SizedBox(height: 10), separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final package = controller.packages[index]; final package = controller.packages[index];
return _buildPackageCard(context, package, controller); return _buildPackageCard(context, package, controller, cs);
}, },
); );
}, },
@@ -56,10 +44,9 @@ class PackageUpdateScreen extends StatelessWidget {
); );
} }
// ─────────────────────────── APP BAR ─────────────────────────── PreferredSizeWidget _buildAppBar(ColorScheme cs) {
PreferredSizeWidget _buildAppBar() {
return AppBar( return AppBar(
backgroundColor: _bg, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
leading: GestureDetector( leading: GestureDetector(
@@ -67,12 +54,12 @@ class PackageUpdateScreen extends StatelessWidget {
child: Container( child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
), ),
child: const Icon(Icons.arrow_back_ios_new_rounded, child: Icon(Icons.arrow_back_ios_new_rounded,
color: _textSecondary, size: 16), color: cs.onSurfaceVariant, size: 16),
), ),
), ),
title: Row( title: Row(
@@ -80,18 +67,18 @@ class PackageUpdateScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(7), padding: const EdgeInsets.all(7),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _accent.withOpacity(0.12), color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(9), borderRadius: BorderRadius.circular(9),
border: Border.all(color: _accent.withOpacity(0.25)), border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
), ),
child: const Icon(Icons.system_update_rounded, child: Icon(Icons.system_update_rounded,
color: _accent, size: 16), color: cs.primary, size: 16),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( Text(
'تحديث التطبيق', 'تحديث التطبيق',
style: TextStyle( style: TextStyle(
color: _textPrimary, color: cs.onSurface,
fontSize: 17, fontSize: 17,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -105,25 +92,24 @@ class PackageUpdateScreen extends StatelessWidget {
margin: const EdgeInsets.only(right: 16), margin: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
), ),
child: const Icon(Icons.refresh_rounded, child: Icon(Icons.refresh_rounded,
color: _textSecondary, size: 18), color: cs.onSurfaceVariant, size: 18),
), ),
), ),
], ],
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1), preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: _divider), child: Container(height: 1, color: cs.outline),
), ),
); );
} }
// ─────────────────────────── PACKAGE CARD ───────────────────────────
Widget _buildPackageCard( Widget _buildPackageCard(
BuildContext context, dynamic package, PackageController controller) { BuildContext context, dynamic package, PackageController controller, ColorScheme cs) {
final platform = package['platform']?.toString() ?? ''; final platform = package['platform']?.toString() ?? '';
final isAndroid = platform.toLowerCase().contains('android'); final isAndroid = platform.toLowerCase().contains('android');
final isIOS = platform.toLowerCase().contains('ios'); final isIOS = platform.toLowerCase().contains('ios');
@@ -131,8 +117,8 @@ class PackageUpdateScreen extends StatelessWidget {
final Color platformColor = isAndroid final Color platformColor = isAndroid
? const Color(0xFF4CAF50) ? const Color(0xFF4CAF50)
: isIOS : isIOS
? _info ? cs.tertiary
: _warning; : cs.tertiary;
final IconData platformIcon = isAndroid final IconData platformIcon = isAndroid
? Icons.android_rounded ? Icons.android_rounded
: isIOS : isIOS
@@ -142,20 +128,19 @@ class PackageUpdateScreen extends StatelessWidget {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => _showUpdateDialog(context, package, controller), onTap: () => _showUpdateDialog(context, package, controller, cs),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
splashColor: _accent.withOpacity(0.06), splashColor: cs.primary.withValues(alpha: 0.06),
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
), ),
child: Row( child: Row(
children: [ children: [
// Platform Icon
Container( Container(
width: 48, width: 48,
height: 48, height: 48,
@@ -164,27 +149,24 @@ class PackageUpdateScreen extends StatelessWidget {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
platformColor.withOpacity(0.20), platformColor.withValues(alpha: 0.20),
platformColor.withOpacity(0.06), platformColor.withValues(alpha: 0.06),
], ],
), ),
borderRadius: BorderRadius.circular(13), borderRadius: BorderRadius.circular(13),
border: Border.all(color: platformColor.withOpacity(0.25)), border: Border.all(color: platformColor.withValues(alpha: 0.25)),
), ),
child: Icon(platformIcon, color: platformColor, size: 22), child: Icon(platformIcon, color: platformColor, size: 22),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
// Info
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
package['appName']?.toString() ?? '—', package['appName']?.toString() ?? '—',
style: const TextStyle( style: TextStyle(
color: _textPrimary, color: cs.onSurface,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -195,31 +177,29 @@ class PackageUpdateScreen extends StatelessWidget {
_buildTag(platform, platformColor), _buildTag(platform, platformColor),
const SizedBox(width: 6), const SizedBox(width: 6),
_buildVersionBadge( _buildVersionBadge(
package['version']?.toString() ?? '?'), package['version']?.toString() ?? '?', cs),
], ],
), ),
], ],
), ),
), ),
// Update button
Container( Container(
padding: padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 7), const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _accent.withOpacity(0.10), color: cs.primary.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: _accent.withOpacity(0.25)), border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
Icon(Icons.edit_rounded, color: _accent, size: 13), Icon(Icons.edit_rounded, color: cs.primary, size: 13),
SizedBox(width: 5), const SizedBox(width: 5),
Text( Text(
'تعديل', 'تعديل',
style: TextStyle( style: TextStyle(
color: _accent, color: cs.primary,
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -238,7 +218,7 @@ class PackageUpdateScreen extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.10), color: color.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
@@ -252,17 +232,17 @@ class PackageUpdateScreen extends StatelessWidget {
); );
} }
Widget _buildVersionBadge(String version) { Widget _buildVersionBadge(String version, ColorScheme cs) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _divider, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
'v$version', 'v$version',
style: const TextStyle( style: TextStyle(
color: _textSecondary, color: cs.onSurfaceVariant,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontFamily: 'monospace', fontFamily: 'monospace',
@@ -271,8 +251,7 @@ class PackageUpdateScreen extends StatelessWidget {
); );
} }
// ─────────────────────────── EMPTY STATE ─────────────────────────── Widget _buildEmptyState(ColorScheme cs) {
Widget _buildEmptyState() {
return Center( return Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -280,30 +259,29 @@ class PackageUpdateScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _surface, color: cs.surfaceContainerHighest,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
), ),
child: const Icon(Icons.inventory_2_outlined, child: Icon(Icons.inventory_2_outlined,
color: _textSecondary, size: 32), color: cs.onSurfaceVariant, size: 32),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('لا توجد حزم متاحة', Text('لا توجد حزم متاحة',
style: TextStyle( style: TextStyle(
color: _textPrimary, color: cs.onSurface,
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600)), fontWeight: FontWeight.w600)),
const SizedBox(height: 6), const SizedBox(height: 6),
const Text('اسحب للأسفل لإعادة التحميل', Text('اسحب للأسفل لإعادة التحميل',
style: TextStyle(color: _textSecondary, fontSize: 12)), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)),
], ],
), ),
); );
} }
// ─────────────────────────── UPDATE DIALOG ───────────────────────────
void _showUpdateDialog( void _showUpdateDialog(
BuildContext context, dynamic package, PackageController controller) { BuildContext context, dynamic package, PackageController controller, ColorScheme cs) {
controller.versionController.clear(); controller.versionController.clear();
Get.dialog( Get.dialog(
@@ -311,9 +289,9 @@ class PackageUpdateScreen extends StatelessWidget {
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _surfaceElevated, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
boxShadow: const [ boxShadow: const [
BoxShadow( BoxShadow(
color: Colors.black54, color: Colors.black54,
@@ -327,107 +305,95 @@ class PackageUpdateScreen extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Header
Row( Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _accent.withOpacity(0.12), color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: _accent.withOpacity(0.25)), border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
), ),
child: const Icon(Icons.system_update_rounded, child: Icon(Icons.system_update_rounded,
color: _accent, size: 20), color: cs.primary, size: 20),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'تحديث الإصدار', 'تحديث الإصدار',
style: TextStyle( style: TextStyle(
color: _textPrimary, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
Text( Text(
package['appName']?.toString() ?? '', package['appName']?.toString() ?? '',
style: const TextStyle( style: TextStyle(
color: _textSecondary, fontSize: 11), color: cs.onSurfaceVariant, fontSize: 11),
), ),
], ],
), ),
), ),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Container(height: 1, color: _divider), Container(height: 1, color: cs.outline),
const SizedBox(height: 20), const SizedBox(height: 20),
// Current info
Row( Row(
children: [ children: [
_buildInfoChip(Icons.devices_rounded, _buildInfoChip(Icons.devices_rounded,
package['platform']?.toString() ?? '', _info), package['platform']?.toString() ?? '', cs.tertiary),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildInfoChip(Icons.tag_rounded, _buildInfoChip(Icons.tag_rounded,
'الحالي: ${package['version']}', _warning), 'الحالي: ${package['version']}', cs.tertiary),
], ],
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
Text(
// Input label
const Text(
'الإصدار الجديد', 'الإصدار الجديد',
style: TextStyle( style: TextStyle(
color: _textSecondary, color: cs.onSurfaceVariant,
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
letterSpacing: 0.8, letterSpacing: 0.8,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// Text input
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _bg, color: cs.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: _divider), border: Border.all(color: cs.outline),
), ),
child: TextField( child: TextField(
controller: controller.versionController, controller: controller.versionController,
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
style: const TextStyle( style: TextStyle(
color: _textPrimary, color: cs.onSurface,
fontSize: 15, fontSize: 15,
fontFamily: 'monospace', fontFamily: 'monospace',
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: package['version'].toString(), hintText: package['version'].toString(),
hintStyle: const TextStyle( hintStyle: TextStyle(
color: _textSecondary, color: cs.onSurfaceVariant,
fontFamily: 'monospace', fontFamily: 'monospace',
), ),
prefixIcon: prefixIcon:
const Icon(Icons.tag_rounded, color: _accent, size: 18), Icon(Icons.tag_rounded, color: cs.primary, size: 18),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14), horizontal: 14, vertical: 14),
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Actions
Row( Row(
children: [ children: [
Expanded( Expanded(
@@ -437,12 +403,12 @@ class PackageUpdateScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: _divider), side: BorderSide(color: cs.outline),
), ),
), ),
child: const Text( child: Text(
'إلغاء', 'إلغاء',
style: TextStyle(color: _textSecondary, fontSize: 13), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
), ),
), ),
), ),
@@ -450,11 +416,11 @@ class PackageUpdateScreen extends StatelessWidget {
Expanded( Expanded(
child: Obx(() => ElevatedButton.icon( child: Obx(() => ElevatedButton.icon(
icon: controller.isLoading.value icon: controller.isLoading.value
? const SizedBox( ? SizedBox(
width: 14, width: 14,
height: 14, height: 14,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: cs.onPrimary,
strokeWidth: 2, strokeWidth: 2,
), ),
) )
@@ -464,8 +430,8 @@ class PackageUpdateScreen extends StatelessWidget {
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: _accent, backgroundColor: cs.primary,
foregroundColor: _bg, foregroundColor: cs.surface,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -495,9 +461,9 @@ class PackageUpdateScreen extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.08), color: color.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.2)), border: Border.all(color: color.withValues(alpha: 0.2)),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -518,9 +484,6 @@ class PackageUpdateScreen extends StatelessWidget {
} }
} }
// ══════════════════════════════════════════════════════════════
// CONTROLLER
// ══════════════════════════════════════════════════════════════
class PackageController extends GetxController { class PackageController extends GetxController {
List packages = []; List packages = [];
var isLoading = false.obs; var isLoading = false.obs;
@@ -2,14 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/passenger_admin_controller.dart'; import '../../../controller/admin/passenger_admin_controller.dart';
import '../../../main.dart'; // للوصول إلى box import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/my_textField.dart';
import '../../widgets/mycircular.dart';
import '../../widgets/snackbar.dart'; import '../../widgets/snackbar.dart';
import 'passenger_details_page.dart'; import 'passenger_details_page.dart';
@@ -21,141 +15,36 @@ class Passengrs extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 1. منطق السوبر أدمن 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 = myPhone == '963942542053' || myPhone == '963992952235'; bool isSuperAdmin =
myPhone == '963942542053' || myPhone == '963992952235';
return MyScafolld( return Scaffold(
title: 'Passengers Management'.tr, backgroundColor: cs.surface,
isleading: true, body: Column(
body: [
// استخدام Expanded أو Container بطول الشاشة لتجنب المشاكل
SizedBox(
height: Get.height, // تأمين مساحة العمل
child: GetBuilder<PassengerAdminController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
}
return Column(
children: [
// --- قسم الإحصائيات والجوائز (Dashboard) ---
Padding(
padding: const EdgeInsets.all(16.0),
child: _buildDashboardCard(context, controller),
),
// --- عنوان القائمة ---
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"All Passengers".tr,
style: AppStyle.title.copyWith(
fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
"${controller.passengersData['message']?.length ?? 0} Users",
style:
TextStyle(color: Colors.grey[600], fontSize: 12),
),
],
),
),
const SizedBox(height: 10),
// --- قائمة الركاب ---
// استخدام Expanded هنا هو الحل الجذري لمكلة Overflow
Expanded(
child: _buildPassengersList(controller, isSuperAdmin),
),
// مساحة سفلية صغيرة لضمان عدم التصاق القائمة بالحافة
const SizedBox(height: 20),
],
);
},
),
),
],
);
}
// --- تصميم بطاقة الإحصائيات (Dashboard) ---
Widget _buildDashboardCard(
BuildContext context, PassengerAdminController controller) {
// جلب العدد بأمان
final String countValue = (controller.passengersData['message'] != null &&
controller.passengersData['message'].isNotEmpty)
? controller.passengersData['message'][0]['countPassenger']
?.toString() ??
'0'
: '0';
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Column(
children: [ children: [
Row( _buildAppBar(context, cs),
children: [ Expanded(
Container( child: GetBuilder<PassengerAdminController>(
padding: const EdgeInsets.all(12), builder: (controller) {
decoration: BoxDecoration( if (controller.isLoading) {
color: AppColor.primaryColor.withOpacity(0.1), return _buildLoadingState(cs);
shape: BoxShape.circle, }
),
child: Icon(Icons.groups_rounded, return Column(
color: AppColor.primaryColor, size: 30), children: [
), _buildDashboardCard(context, controller, cs),
const SizedBox(width: 15), _buildListHeader(context, controller, cs),
Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: _buildPassengersList(
children: [ controller, isSuperAdmin, cs),
Text( ),
'Total Passengers'.tr, const SizedBox(height: 20),
style: const TextStyle(fontSize: 14, color: Colors.grey), ],
), );
Text(
countValue,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
),
],
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton.icon(
icon: const Icon(Icons.card_giftcard,
color: Colors.white, size: 20),
label: Text('Add Prize to Gold Passengers'.tr,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.yellowColor, // لون ذهبي
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
onPressed: () {
_showAddPrizeDialog(controller);
}, },
), ),
), ),
@@ -164,20 +53,252 @@ class Passengrs extends StatelessWidget {
); );
} }
// --- بناء قائمة الركاب --- Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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.groups_rounded, color: cs.primary, size: 18),
),
const SizedBox(width: 10),
Text(
'إدارة الركاب',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل الركاب...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildDashboardCard(
BuildContext context,
PassengerAdminController controller,
ColorScheme cs) {
final String countValue =
(controller.passengersData['message'] != null &&
controller.passengersData['message'].isNotEmpty)
? controller.passengersData['message'][0]['countPassenger']
?.toString() ??
'0'
: '0';
return Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.primary.withValues(alpha: 0.15)),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
cs.primary.withValues(alpha: 0.20),
cs.primary.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: cs.primary.withValues(alpha: 0.25)),
),
child: Icon(Icons.groups_rounded,
color: cs.primary, size: 28),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'إجمالي الركاب',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 4),
Text(
countValue,
style: TextStyle(
color: cs.onSurface,
fontSize: 26,
fontWeight: FontWeight.w800,
height: 1,
),
),
],
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
icon: const Icon(Icons.card_giftcard,
color: Colors.white, size: 18),
label: Text('إضافة جائزة لركاب الذهب',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF59E0B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14)),
elevation: 0,
),
onPressed: () {
_showAddPrizeDialog(controller, cs);
},
),
),
],
),
),
);
}
Widget _buildListHeader(
BuildContext context,
PassengerAdminController controller,
ColorScheme cs) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
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.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 10),
Expanded(
child: Container(height: 1, color: cs.outline),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: cs.primary.withValues(alpha: 0.2)),
),
child: Text(
'${controller.passengersData['message']?.length ?? 0}',
style: TextStyle(
color: cs.primary,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
Widget _buildPassengersList( Widget _buildPassengersList(
PassengerAdminController controller, bool isSuperAdmin) { PassengerAdminController controller,
final List<dynamic> passengers = controller.passengersData['message'] ?? []; bool isSuperAdmin,
ColorScheme cs) {
final List<dynamic> passengers =
controller.passengersData['message'] ?? [];
if (passengers.isEmpty) { if (passengers.isEmpty) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.person_off_outlined, size: 60, color: Colors.grey[300]), Container(
const SizedBox(height: 10), padding: const EdgeInsets.all(20),
Text("No passengers found".tr, decoration: BoxDecoration(
style: TextStyle(color: Colors.grey[400])), color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.person_off_outlined,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد ركاب',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 14)),
], ],
), ),
); );
@@ -187,132 +308,171 @@ class Passengrs extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemCount: passengers.length, itemCount: passengers.length,
separatorBuilder: (context, index) => const SizedBox(height: 12), separatorBuilder: (context, index) => const SizedBox(height: 10),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final user = passengers[index]; final user = passengers[index];
return _buildPassengerItem(user, isSuperAdmin); return _buildPassengerItem(user, isSuperAdmin, cs);
}, },
); );
} }
// --- عنصر الراكب الواحد (Card) --- Widget _buildPassengerItem(
Widget _buildPassengerItem(dynamic user, bool isSuperAdmin) { dynamic user, bool isSuperAdmin, ColorScheme cs) {
String firstName = user['first_name'] ?? ''; String firstName = user['first_name'] ?? '';
String lastName = user['last_name'] ?? ''; String lastName = user['last_name'] ?? '';
String fullName = '$firstName $lastName'.trim(); String fullName = '$firstName $lastName'.trim();
if (fullName.isEmpty) fullName = 'Unknown User'; if (fullName.isEmpty) fullName = 'مستخدم';
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.1)), border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(16),
onTap: () { onTap: () {
// الانتقال للتفاصيل مع تمرير صلاحية الأدمن
Get.to( Get.to(
() => const PassengerDetailsPage(), () => const PassengerDetailsPage(),
arguments: {'data': user, 'isSuperAdmin': isSuperAdmin}, arguments: {'data': user, 'isSuperAdmin': isSuperAdmin},
); );
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(14),
child: Row( child: Row(
children: [ children: [
// Avatar Container(
CircleAvatar( width: 48,
radius: 25, height: 48,
backgroundColor: AppColor.primaryColor.withOpacity(0.1), decoration: BoxDecoration(
child: Text( gradient: LinearGradient(
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'U', begin: Alignment.topLeft,
style: TextStyle( end: Alignment.bottomRight,
color: AppColor.primaryColor, colors: [
fontWeight: FontWeight.bold, cs.primary.withValues(alpha: 0.20),
fontSize: 18), cs.primary.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: cs.primary.withValues(alpha: 0.2)),
),
child: Center(
child: Text(
fullName.isNotEmpty
? fullName[0].toUpperCase()
: 'U',
style: TextStyle(
color: cs.primary,
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 14),
// Info
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
fullName, fullName,
style: const TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16), color: cs.onSurface,
fontWeight: FontWeight.w600,
fontSize: 15,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 6),
// Stats Row
Row( Row(
children: [ children: [
Icon(Icons.star_rounded, Container(
size: 16, color: Colors.amber[700]), padding: const EdgeInsets.symmetric(
Text( horizontal: 6, vertical: 2),
" ${user['ratingPassenger'] ?? '0.0'} ", decoration: BoxDecoration(
style: const TextStyle( color: const Color(0xFFF59E0B)
fontSize: 12, fontWeight: FontWeight.bold), .withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star_rounded,
size: 12, color: Color(0xFFF59E0B)),
const SizedBox(width: 3),
Text(
user['ratingPassenger']?.toString() ??
'0.0',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFFF59E0B),
),
),
],
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Icon(Icons.directions_car, Container(
size: 14, color: Colors.grey[400]), padding: const EdgeInsets.symmetric(
Text( horizontal: 6, vertical: 2),
" ${user['countPassengerRide'] ?? '0'} Trips", decoration: BoxDecoration(
style: TextStyle( color: cs.primary.withValues(alpha: 0.1),
fontSize: 12, color: Colors.grey[600]), borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.directions_car_rounded,
size: 12, color: cs.primary),
const SizedBox(width: 3),
Text(
'${user['countPassengerRide'] ?? '0'} رحلة',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: cs.primary,
),
),
],
),
), ),
], ],
), ),
const SizedBox(height: 6),
const SizedBox(height: 4),
// Phone Number (Masked logic)
Row( Row(
children: [ children: [
Icon(Icons.phone_iphone, Icon(Icons.phone_iphone_rounded,
size: 12, color: Colors.grey[400]), size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
_formatPhoneNumber( _formatPhoneNumber(
user['phone'].toString(), isSuperAdmin), user['phone'].toString(), isSuperAdmin),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 11,
color: Colors.grey[500], color: cs.onSurfaceVariant,
fontFamily: 'monospace'), fontFamily: 'monospace',
),
), ),
], ],
), ),
// Email (Show only if Super Admin)
if (isSuperAdmin && user['email'] != null) ...[ if (isSuperAdmin && user['email'] != null) ...[
const SizedBox(height: 2), const SizedBox(height: 3),
Text( Text(
user['email'], user['email'],
style: style: TextStyle(
TextStyle(fontSize: 10, color: Colors.grey[400]), fontSize: 10, color: cs.onSurfaceVariant),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
) ),
] ],
], ],
), ),
), ),
// Arrow
Icon(Icons.arrow_forward_ios_rounded, Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: Colors.grey[300]), size: 14, color: cs.onSurfaceVariant),
], ],
), ),
), ),
@@ -321,63 +481,89 @@ class Passengrs extends StatelessWidget {
); );
} }
// --- دالة تنسيق الرقم (إظهار آخر 4 أرقام لغير الأدمن) ---
String _formatPhoneNumber(String phone, bool isSuperAdmin) { String _formatPhoneNumber(String phone, bool isSuperAdmin) {
if (isSuperAdmin) return phone; // إظهار الرقم كاملاً للسوبر أدمن if (isSuperAdmin) return phone;
// لغير الأدمن
if (phone.length <= 4) return phone; if (phone.length <= 4) return phone;
String lastFour = phone.substring(phone.length - 4); String lastFour = phone.substring(phone.length - 4);
String masked = '*' * (phone.length - 4); String masked = '*' * (phone.length - 4);
return '$masked$lastFour'; // النتيجة: *******5678 return '$masked$lastFour';
} }
// --- دالة إضافة الجوائز --- void _showAddPrizeDialog(
void _showAddPrizeDialog(PassengerAdminController controller) { PassengerAdminController controller, ColorScheme cs) {
// التحقق من يوم السبت
if (DateTime.now().weekday == DateTime.saturday) { if (DateTime.now().weekday == DateTime.saturday) {
Get.defaultDialog( Get.defaultDialog(
title: 'Add Prize'.tr, title: 'إضافة جائزة',
titleStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), titleStyle: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: cs.onSurface),
backgroundColor: cs.surface,
contentPadding: const EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
content: Form( content: Column(
key: controller.formPrizeKey, children: [
child: Column( Text(
children: [ 'إضافة نقاط لمحفظة ركاب الذهب',
Text( textAlign: TextAlign.center,
'Add Points to Gold Passengers wallet'.tr, style:
textAlign: TextAlign.center, TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
style: TextStyle(fontSize: 14, color: Colors.grey[700]), ),
const SizedBox(height: 20),
TextFormField(
controller: controller.passengerPrizeController,
keyboardType: TextInputType.number,
style: TextStyle(color: cs.onSurface),
cursorColor: cs.primary,
decoration: InputDecoration(
labelText: 'مبلغ الجائزة',
hintText: '1000...',
filled: true,
fillColor: cs.surfaceContainerHighest,
labelStyle: TextStyle(color: cs.onSurfaceVariant),
hintStyle: TextStyle(color: cs.onSurfaceVariant),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: cs.outline),
borderRadius: BorderRadius.circular(14),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: cs.primary, width: 1.5),
borderRadius: BorderRadius.circular(14),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 14),
), ),
const SizedBox(height: 20), ),
MyTextForm( ],
controller: controller.passengerPrizeController,
label: 'Prize Amount'.tr,
hint: '1000...',
type: TextInputType.number,
),
],
),
), ),
confirm: SizedBox( confirm: SizedBox(
width: 120, width: 120,
child: MyElevatedButton( child: ElevatedButton(
title: 'Add', style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
onPressed: () async { onPressed: () async {
if (controller.formPrizeKey.currentState!.validate()) { if (controller.formPrizeKey.currentState!.validate()) {
controller.addPassengerPrizeToWalletSecure(); controller.addPassengerPrizeToWalletSecure();
Get.back(); Get.back();
} }
}, },
child: const Text('إضافة',
style: TextStyle(fontWeight: FontWeight.bold)),
), ),
), ),
cancel: TextButton( cancel: TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
child: child: Text('إلغاء',
Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))), style: TextStyle(color: cs.onSurfaceVariant)),
),
); );
} else { } else {
mySnackbarWarning('Prizes can only be added on Saturdays.'.tr); mySnackbarWarning('يمكن إضافة الجوائز يوم السبت فقط');
} }
} }
} }
@@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/kazan_controller.dart'; import '../../../controller/admin/kazan_controller.dart';
import '../../../views/widgets/snackbar.dart'; import '../../../views/widgets/snackbar.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
class KazanEditorPage extends StatelessWidget { class KazanEditorPage extends StatelessWidget {
@@ -14,63 +12,72 @@ class KazanEditorPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld( final cs = Theme.of(context).colorScheme;
title: 'تعديل أسعار كازان'.tr,
isleading: true, return Scaffold(
body: [ backgroundColor: cs.surface,
Column( appBar: AppBar(
children: [ backgroundColor: cs.surface,
_buildCountryDropdown(), elevation: 0,
Expanded( centerTitle: true,
child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty title: Text('تعديل أسعار كازان'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
? const Center(child: CircularProgressIndicator()) leading: GestureDetector(
: SingleChildScrollView( onTap: () => Get.back(),
padding: const EdgeInsets.all(16), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionHeader('⚙️ الإعدادات العامة'),
_buildGeneralSettings(),
const SizedBox(height: 24),
_buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'),
_buildKmPricesGrid(),
const SizedBox(height: 24),
_buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'),
_buildMinutePrices(),
const SizedBox(height: 32),
MyElevatedButton(
title: '💾 حفظ جميع التعديلات',
icon: Icons.save_rounded,
onPressed: () => _handleSave(),
),
const SizedBox(height: 100),
],
),
)),
),
],
), ),
], ),
body: 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),
],
),
)),
),
],
),
); );
} }
Widget _buildCountryDropdown() { Widget _buildCountryDropdown(ColorScheme cs) {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Obx(() => Container( child: Obx(() => Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<String>( child: DropdownButton<String>(
value: controller.selectedCountry.value, value: controller.selectedCountry.value,
isExpanded: true, isExpanded: true,
icon: const Icon(Icons.keyboard_arrow_down_rounded), icon: const Icon(Icons.keyboard_arrow_down_rounded),
style: AppStyle.title.copyWith(fontSize: 16), style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16, color: cs.onSurface),
items: controller.countries.map((c) { items: controller.countries.map((c) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: c['name'], value: c['name'],
@@ -92,13 +99,14 @@ class KazanEditorPage extends StatelessWidget {
); );
} }
// ==================================================================== Widget _buildGeneralSettings(ColorScheme cs) {
// 1. الإعدادات العامة
// ====================================================================
Widget _buildGeneralSettings() {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column( child: Column(
children: [ children: [
_buildSliderItem( _buildSliderItem(
@@ -106,31 +114,30 @@ class KazanEditorPage extends StatelessWidget {
'kazanPercent', 'kazanPercent',
'النسبة المئوية التي تقتطعها المنصة من كل رحلة', 'النسبة المئوية التي تقتطعها المنصة من كل رحلة',
Icons.percent_rounded, Icons.percent_rounded,
cs,
), ),
const Divider(height: 32, color: AppColor.divider), Divider(height: 32, color: cs.outline),
_buildPriceInputRow( _buildPriceInputRow(
'سعر الوقود المرجعي', 'سعر الوقود المرجعي',
'fuelPrice', 'fuelPrice',
'السعر المستخدم في حسابات تعويض الوقود', 'السعر المستخدم في حسابات تعويض الوقود',
Icons.local_gas_station_rounded, Icons.local_gas_station_rounded,
cs,
), ),
const Divider(height: 32, color: AppColor.divider), Divider(height: 32, color: cs.outline),
_buildPriceInputRow( _buildPriceInputRow(
'رمز العملة', 'رمز العملة',
'currency', 'currency',
'مثال: SYP, EGP, JOD, IQD', 'مثال: SYP, EGP, JOD, IQD',
Icons.currency_exchange_rounded, Icons.currency_exchange_rounded,
cs,
), ),
], ],
), ),
); );
} }
// ==================================================================== Widget _buildKmPricesGrid(ColorScheme cs) {
// 2. أسعار الكيلومتر - كل نوع سيارة له حقل خاص به
// ====================================================================
Widget _buildKmPricesGrid() {
// 🆕 9 أنواع سيارات - كل واحد له عمود سعر مستقل
final Map<String, Map<String, dynamic>> priceFields = { final Map<String, Map<String, dynamic>> priceFields = {
'speedPrice': { 'speedPrice': {
'label': 'Speed ⚡', 'label': 'Speed ⚡',
@@ -180,13 +187,17 @@ class KazanEditorPage extends StatelessWidget {
}; };
return Container( return Container(
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: GridView.builder( child: GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 3 أعمدة بدلاً من 2 crossAxisCount: 3,
childAspectRatio: 1.8, childAspectRatio: 1.8,
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
@@ -196,14 +207,14 @@ class KazanEditorPage extends StatelessWidget {
String key = priceFields.keys.elementAt(index); String key = priceFields.keys.elementAt(index);
var field = priceFields[key]!; var field = priceFields[key]!;
return _buildKmPriceCard( return _buildKmPriceCard(
key, field['label'], field['icon'], field['color']); key, field['label'], field['icon'], field['color'], cs);
}, },
), ),
); );
} }
Widget _buildKmPriceCard( Widget _buildKmPriceCard(
String key, String label, IconData icon, Color color) { String key, String label, IconData icon, Color color, ColorScheme cs) {
final TextEditingController textController = final TextEditingController textController =
TextEditingController(text: _getValue(key)); TextEditingController(text: _getValue(key));
@@ -225,8 +236,7 @@ class KazanEditorPage extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
label, label,
style: AppStyle.caption style: GoogleFonts.inter(fontSize: 10, fontWeight: FontWeight.bold, color: cs.onSurfaceVariant),
.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@@ -240,7 +250,7 @@ class KazanEditorPage extends StatelessWidget {
controller: textController, controller: textController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppStyle.number.copyWith(fontSize: 13, color: color), style: GoogleFonts.jetBrainsMono(fontSize: 13, fontWeight: FontWeight.bold, color: color),
decoration: const InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
@@ -256,10 +266,7 @@ class KazanEditorPage extends StatelessWidget {
); );
} }
// ==================================================================== Widget _buildMinutePrices(ColorScheme cs) {
// 3. أسعار الدقيقة (حسب وقت اليوم)
// ====================================================================
Widget _buildMinutePrices() {
final Map<String, Map<String, dynamic>> minuteFields = { final Map<String, Map<String, dynamic>> minuteFields = {
'normalMinPrice': { 'normalMinPrice': {
'label': 'Normal (سعر الدقيقة العادي)', 'label': 'Normal (سعر الدقيقة العادي)',
@@ -282,7 +289,11 @@ class KazanEditorPage extends StatelessWidget {
}; };
return Container( return Container(
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: minuteFields.entries.map((entry) { children: minuteFields.entries.map((entry) {
@@ -293,6 +304,7 @@ class KazanEditorPage extends StatelessWidget {
entry.key, entry.key,
entry.value['desc'], entry.value['desc'],
entry.value['icon'], entry.value['icon'],
cs,
), ),
); );
}).toList(), }).toList(),
@@ -300,15 +312,12 @@ class KazanEditorPage extends StatelessWidget {
); );
} }
// ==================================================================== Widget _buildSectionHeader(String title, ColorScheme cs) {
// دوال مساعدة للـ UI
// ====================================================================
Widget _buildSectionHeader(String title) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12, left: 4), padding: const EdgeInsets.only(bottom: 12, left: 4),
child: Text( child: Text(
title, title,
style: AppStyle.title.copyWith(color: AppColor.accent), style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.primary),
), ),
); );
} }
@@ -322,21 +331,21 @@ class KazanEditorPage extends StatelessWidget {
} }
Widget _buildPriceInputRow( Widget _buildPriceInputRow(
String title, String key, String desc, IconData icon) { String title, String key, String desc, IconData icon, ColorScheme cs) {
final TextEditingController textController = final TextEditingController textController =
TextEditingController(text: _getValue(key)); TextEditingController(text: _getValue(key));
return Row( return Row(
children: [ children: [
Icon(icon, size: 20, color: AppColor.accent), Icon(icon, size: 20, color: cs.primary),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(title, Text(title,
style: AppStyle.body.copyWith(fontWeight: FontWeight.bold)), style: GoogleFonts.inter(fontSize: 14, color: cs.onSurface, fontWeight: FontWeight.bold)),
Text(desc, style: AppStyle.caption.copyWith(fontSize: 10)), Text(desc, style: GoogleFonts.inter(fontSize: 10, color: cs.onSurfaceVariant)),
], ],
), ),
), ),
@@ -344,16 +353,15 @@ class KazanEditorPage extends StatelessWidget {
width: 100, width: 100,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surfaceElevated, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: TextField( child: TextField(
controller: textController, controller: textController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style: GoogleFonts.jetBrainsMono(fontSize: 16, fontWeight: FontWeight.bold, color: cs.primary),
AppStyle.number.copyWith(fontSize: 16, color: AppColor.accent),
decoration: const InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
@@ -367,7 +375,7 @@ class KazanEditorPage extends StatelessWidget {
} }
Widget _buildSliderItem( Widget _buildSliderItem(
String title, String key, String desc, IconData icon) { String title, String key, String desc, IconData icon, ColorScheme cs) {
double value = double.tryParse(_getValue(key)) ?? 0; double value = double.tryParse(_getValue(key)) ?? 0;
return Column( return Column(
@@ -378,25 +386,25 @@ class KazanEditorPage extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
Icon(icon, size: 18, color: AppColor.accent), Icon(icon, size: 18, color: cs.primary),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(title, style: AppStyle.title), Text(title, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
], ],
), ),
Text( Text(
'${value.toInt()}%', '${value.toInt()}%',
style: AppStyle.number.copyWith(fontSize: 18), style: GoogleFonts.jetBrainsMono(fontSize: 18, fontWeight: FontWeight.bold, color: cs.onSurface),
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text(desc, style: AppStyle.caption), Text(desc, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
Slider( Slider(
value: value.clamp(0, 100), value: value.clamp(0, 100),
min: 0, min: 0,
max: 100, max: 100,
activeColor: AppColor.accent, activeColor: cs.primary,
inactiveColor: AppColor.divider, inactiveColor: cs.outline,
onChanged: (val) { onChanged: (val) {
_setValue(key, val.toInt().toString()); _setValue(key, val.toInt().toString());
}, },
@@ -405,14 +413,10 @@ class KazanEditorPage extends StatelessWidget {
); );
} }
// ====================================================================
// حفظ البيانات
// ====================================================================
void _handleSave() async { void _handleSave() async {
final data = Map<String, dynamic>.from(controller.kazanData); final data = Map<String, dynamic>.from(controller.kazanData);
data['adminId'] = 'admin1'; data['adminId'] = 'admin1';
// التأكد من وجود country
if (!data.containsKey('country') || if (!data.containsKey('country') ||
data['country'] == null || data['country'] == null ||
data['country'].toString().isEmpty) { data['country'].toString().isEmpty) {
@@ -1,9 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/promo_controller.dart'; import '../../../controller/admin/promo_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
import '../../widgets/my_textField.dart'; import '../../widgets/my_textField.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
@@ -15,89 +13,110 @@ class PromoManagementPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld( final cs = Theme.of(context).colorScheme;
title: 'إدارة أكواد الخصم'.tr,
isleading: true, return Scaffold(
action: IconButton( backgroundColor: cs.surface,
icon: const Icon(Icons.add_circle_outline_rounded, color: AppColor.accent), appBar: AppBar(
onPressed: () => _showPromoSheet(context), 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),
),
actions: [
IconButton(
icon: Icon(Icons.add_circle_outline_rounded, color: cs.primary),
onPressed: () => _showPromoSheet(context),
),
],
), ),
body: [ body: Obx(() => controller.isLoading.value && controller.promoList.isEmpty
Obx(() => controller.isLoading.value && controller.promoList.isEmpty ? Center(child: CircularProgressIndicator(color: cs.primary))
? const Center(child: CircularProgressIndicator()) : controller.promoList.isEmpty
: controller.promoList.isEmpty ? Center(
? Center( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ Container(
Icon(Icons.confirmation_number_outlined, size: 64, color: AppColor.textMuted), padding: const EdgeInsets.all(20),
const SizedBox(height: 16), decoration: BoxDecoration(shape: BoxShape.circle, color: cs.onSurfaceVariant.withValues(alpha: 0.1)),
Text('لا يوجد أكواد خصم حالياً', style: AppStyle.subtitle), child: Icon(Icons.confirmation_number_outlined, size: 48, color: cs.onSurfaceVariant),
], ),
), const SizedBox(height: 16),
) Text('لا يوجد أكواد خصم حالياً', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 14)),
: RefreshIndicator( ],
onRefresh: () => controller.getPromos(), ),
child: ListView.builder( )
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), : RefreshIndicator(
itemCount: controller.promoList.length, onRefresh: () => controller.getPromos(),
itemBuilder: (context, index) { child: ListView.builder(
final promo = controller.promoList[index]; padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
return _buildPromoCard(context, promo); itemCount: controller.promoList.length,
}, itemBuilder: (context, index) {
), final promo = controller.promoList[index];
)), return _buildPromoCard(context, promo);
], },
),
)),
); );
} }
Widget _buildPromoCard(BuildContext context, dynamic promo) { Widget _buildPromoCard(BuildContext context, dynamic promo) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: AppStyle.cardDecoration, decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: Container( leading: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.accentSoft, color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: const Icon(Icons.local_offer_rounded, color: AppColor.accent), child: Icon(Icons.local_offer_rounded, color: cs.primary),
), ),
title: Text( title: Text(
promo['promo_code']?.toString() ?? 'N/A', promo['promo_code']?.toString() ?? 'N/A',
style: AppStyle.title, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface),
), ),
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text(promo['description']?.toString() ?? '', style: AppStyle.caption), Text(promo['description']?.toString() ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
Icon(Icons.money_rounded, size: 14, color: AppColor.success), Icon(Icons.money_rounded, size: 14, color: const Color(0xFF10B981)),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('% ${promo['amount']}', style: AppStyle.number.copyWith(color: AppColor.success)), Text('% ${promo['amount']}', style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, color: const Color(0xFF10B981))),
const SizedBox(width: 12), const SizedBox(width: 12),
Icon(Icons.person_rounded, size: 14, color: AppColor.info), Icon(Icons.person_rounded, size: 14, color: cs.tertiary),
const SizedBox(width: 4), const SizedBox(width: 4),
Text(promo['passengerID'] == 'none' ? 'عام' : 'مخصص', style: AppStyle.caption), Text(promo['passengerID'] == 'none' ? 'عام' : 'مخصص', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
], ],
), ),
], ],
), ),
trailing: PopupMenuButton( trailing: PopupMenuButton(
icon: const Icon(Icons.more_vert_rounded, color: AppColor.textSecondary), icon: Icon(Icons.more_vert_rounded, color: cs.onSurfaceVariant),
color: AppColor.surfaceElevated, color: cs.surfaceContainerHighest,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 'edit', value: 'edit',
child: Row( child: Row(
children: [ children: [
const Icon(Icons.edit_rounded, size: 18, color: AppColor.info), Icon(Icons.edit_rounded, size: 18, color: cs.tertiary),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('تعديل'.tr), Text('تعديل'.tr),
], ],
@@ -107,9 +126,9 @@ class PromoManagementPage extends StatelessWidget {
value: 'delete', value: 'delete',
child: Row( child: Row(
children: [ children: [
const Icon(Icons.delete_outline_rounded, size: 18, color: AppColor.danger), Icon(Icons.delete_outline_rounded, size: 18, color: cs.error),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('حذف'.tr, style: const TextStyle(color: AppColor.danger)), Text('حذف'.tr, style: TextStyle(color: cs.error)),
], ],
), ),
), ),
@@ -131,6 +150,7 @@ class PromoManagementPage extends StatelessWidget {
} }
void _showPromoSheet(BuildContext context, {dynamic promo}) { void _showPromoSheet(BuildContext context, {dynamic promo}) {
final cs = Theme.of(context).colorScheme;
final TextEditingController codeController = TextEditingController(text: promo?['promo_code']); final TextEditingController codeController = TextEditingController(text: promo?['promo_code']);
final TextEditingController amountController = TextEditingController(text: promo?['amount']?.toString()); final TextEditingController amountController = TextEditingController(text: promo?['amount']?.toString());
final TextEditingController descController = TextEditingController(text: promo?['description']); final TextEditingController descController = TextEditingController(text: promo?['description']);
@@ -141,9 +161,9 @@ class PromoManagementPage extends StatelessWidget {
Get.bottomSheet( Get.bottomSheet(
Container( Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColor.surfaceElevated, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
@@ -152,10 +172,10 @@ class PromoManagementPage extends StatelessWidget {
Container( Container(
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration(color: AppColor.divider, borderRadius: BorderRadius.circular(2)), decoration: BoxDecoration(color: cs.outline, borderRadius: BorderRadius.circular(2)),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text(promo == null ? 'إضافة كود خصم جديد' : 'تعديل كود الخصم', style: AppStyle.headTitle), Text(promo == null ? 'إضافة كود خصم جديد' : 'تعديل كود الخصم', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)),
const SizedBox(height: 24), const SizedBox(height: 24),
MyTextForm( MyTextForm(
controller: codeController, controller: codeController,
@@ -7,143 +7,324 @@ class BlacklistPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
Get.put(QualityController()).fetchBlacklist(); Get.put(QualityController()).fetchBlacklist();
return DefaultTabController( return Scaffold(
length: 2, backgroundColor: cs.surface,
child: Scaffold( body: Column(
appBar: AppBar( children: [
title: const Text('إدارة القائمة السوداء (Blacklist)', _buildAppBar(context, cs),
style: TextStyle(fontWeight: FontWeight.bold)), _buildTabBar(context, cs),
backgroundColor: Colors.red[800], Expanded(
bottom: const TabBar( child: GetBuilder<QualityController>(
indicatorColor: Colors.white, builder: (controller) {
tabs: [ if (controller.isLoading) {
Tab(icon: Icon(Icons.drive_eta), text: 'السائقين المحظورين'), return _buildLoadingState(cs);
Tab(icon: Icon(Icons.person), text: 'الركاب المحظورين'), }
],
),
),
body: GetBuilder<QualityController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: CircularProgressIndicator());
}
return TabBarView( return TabBarView(
children: [ controller: DefaultTabController.of(context),
_buildDriverList(controller), children: [
_buildPassengerList(controller), _buildDriverList(controller, cs),
], _buildPassengerList(controller, cs),
); ],
}, );
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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.error.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.error.withValues(alpha: 0.25)),
),
child: Icon(Icons.block_rounded, color: cs.error, size: 18),
),
const SizedBox(width: 10),
Text(
'القائمة السوداء',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildTabBar(BuildContext context, ColorScheme cs) {
return Container(
margin: const EdgeInsets.fromLTRB(16, 12, 16, 0),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
child: TabBar(
controller: DefaultTabController.of(context),
indicator: BoxDecoration(
color: cs.primary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.primary.withValues(alpha: 0.3)),
),
indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent,
labelColor: cs.primary,
unselectedLabelColor: cs.onSurfaceVariant,
labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13),
padding: const EdgeInsets.all(4),
labelPadding: EdgeInsets.zero,
tabs: const [
Tab(text: 'السائقين', icon: Icon(Icons.drive_eta_rounded, size: 16)),
Tab(text: 'الركاب', icon: Icon(Icons.person_rounded, size: 16)),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري التحميل...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildDriverList(QualityController controller, ColorScheme cs) {
if (controller.driversBlacklist.isEmpty) {
return _buildEmptyState('لا يوجد سائقين محظورين', cs);
}
return ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 80),
physics: const BouncingScrollPhysics(),
itemCount: controller.driversBlacklist.length,
itemBuilder: (context, index) {
var driver = controller.driversBlacklist[index];
return _buildBlacklistItem(
phone: driver['phone'],
reason: driver['reason'] ?? 'غير محدد',
date: driver['created_at'],
type: 'سائق',
icon: Icons.drive_eta_rounded,
onUnblock: () => controller.unblockDriver(driver['phone'].toString()),
cs: cs,
);
},
);
}
Widget _buildPassengerList(QualityController controller, ColorScheme cs) {
if (controller.passengersBlacklist.isEmpty) {
return _buildEmptyState('لا يوجد ركاب محظورين', cs);
}
return ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 80),
physics: const BouncingScrollPhysics(),
itemCount: controller.passengersBlacklist.length,
itemBuilder: (context, index) {
var passenger = controller.passengersBlacklist[index];
return _buildBlacklistItem(
phone: passenger['phone'] ?? passenger['phone_normalized'],
reason: passenger['reason'] ?? 'غير محدد',
date: passenger['created_at'],
type: 'راكب',
icon: Icons.person_rounded,
onUnblock: () => controller.unblockPassenger(
passenger['phone_normalized'].toString()),
cs: cs,
);
},
);
}
Widget _buildEmptyState(String message, ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.block_rounded, size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text(message,
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildBlacklistItem({
required String phone,
required String reason,
required String date,
required String type,
required IconData icon,
required VoidCallback onUnblock,
required ColorScheme cs,
}) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.error.withValues(alpha: 0.15)),
),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: cs.error.withValues(alpha: 0.12),
shape: BoxShape.circle,
border: Border.all(color: cs.error.withValues(alpha: 0.2)),
),
child: Icon(icon, color: cs.error, size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
phone,
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.w700,
fontSize: 15,
fontFamily: 'monospace',
),
),
const SizedBox(height: 4),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: cs.error.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
type,
style: TextStyle(
color: cs.error,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 6),
Expanded(
child: Text(
'السبب: $reason',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 3),
Text(
date,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
],
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () => _showUnblockDialog(Get.context!, type, phone, onUnblock, cs),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)),
),
child: Icon(Icons.settings_backup_restore_rounded,
color: const Color(0xFF10B981), size: 18),
),
),
],
), ),
), ),
); );
} }
Widget _buildDriverList(QualityController controller) { void _showUnblockDialog(
if (controller.driversBlacklist.isEmpty) { BuildContext context, String type, String identifier, VoidCallback onConfirm, ColorScheme cs) {
return const Center(child: Text('لا يوجد سائقين محظورين حالياً'));
}
return ListView.builder(
itemCount: controller.driversBlacklist.length,
padding: const EdgeInsets.all(12),
itemBuilder: (context, index) {
var driver = controller.driversBlacklist[index];
return Card(
elevation: 3,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.redAccent,
child: Icon(Icons.block, color: Colors.white),
),
title: Text('هاتف: ${driver['phone']}',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('السبب: ${driver['reason'] ?? "غير محدد"}'),
Text('تاريخ الحظر: ${driver['created_at']}'),
],
),
trailing: IconButton(
icon: const Icon(Icons.settings_backup_restore,
color: Colors.green),
onPressed: () {
_showUnblockDialog(
Get.context!,
'سائق',
driver['phone'],
() => controller.unblockDriver(driver['phone'].toString()),
);
},
),
),
);
},
);
}
Widget _buildPassengerList(QualityController controller) {
if (controller.passengersBlacklist.isEmpty) {
return const Center(child: Text('لا يوجد ركاب محظورين حالياً'));
}
return ListView.builder(
itemCount: controller.passengersBlacklist.length,
padding: const EdgeInsets.all(12),
itemBuilder: (context, index) {
var passenger = controller.passengersBlacklist[index];
return Card(
elevation: 3,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.orangeAccent,
child: Icon(Icons.person_off, color: Colors.white),
),
title: Text(
'هاتف: ${passenger['phone'] ?? passenger['phone_normalized']}',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('السبب: ${passenger['reason'] ?? "غير محدد"}'),
Text('تاريخ الحظر: ${passenger['created_at']}'),
],
),
trailing: IconButton(
icon: const Icon(Icons.settings_backup_restore,
color: Colors.green),
onPressed: () {
_showUnblockDialog(
Get.context!,
'راكب',
passenger['phone_normalized'],
() => controller.unblockPassenger(
passenger['phone_normalized'].toString()),
);
},
),
),
);
},
);
}
void _showUnblockDialog(BuildContext context, String type, String identifier,
VoidCallback onConfirm) {
Get.defaultDialog( Get.defaultDialog(
title: "تأكيد فك الحظر", title: "تأكيد فك الحظر",
titleStyle: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold),
backgroundColor: cs.surface,
middleText: middleText:
"هل أنت متأكد من فك الحظر عن هذا ال$type ($identifier)؟\nسيتم تسجيل هذه العملية في الـ Audit Log.", "هل أنت متأكد من فك الحظر عن هذا ال$type ($identifier)؟\nسيتم تسجيل هذه العملية في الـ Audit Log.",
textConfirm: "نعم، فك الحظر", textConfirm: "نعم، فك الحظر",
textCancel: "تراجع", textCancel: "تراجع",
confirmTextColor: Colors.white, confirmTextColor: Colors.white,
buttonColor: Colors.green, buttonColor: const Color(0xFF10B981),
cancelTextColor: cs.onSurfaceVariant,
onConfirm: () { onConfirm: () {
Get.back(); Get.back();
onConfirm(); onConfirm();
+418 -219
View File
@@ -1,236 +1,435 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/ride_admin_controller.dart'; import '../../../controller/admin/ride_admin_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mycircular.dart';
class Rides extends StatelessWidget { class Rides extends StatelessWidget {
Rides({super.key}); Rides({super.key});
final RideAdminController rideAdminController = Get.put(RideAdminController()); final RideAdminController rideAdminController = Get.put(RideAdminController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld(title: 'Rides'.tr, isleading: true, body: [ final cs = Theme.of(context).colorScheme;
GetBuilder<RideAdminController>(
builder: (rideAdminController) => rideAdminController.isLoading
? const Center(child: MyCircularProgressIndicator())
: Column(
children: [
SizedBox(
height: Get.height * .4,
child: LineChart(
duration: const Duration(milliseconds: 150),
curve: Curves.ease,
LineChartData(
lineBarsData: [
LineChartBarData(
spots: rideAdminController.chartData,
isCurved: true,
color: Colors.deepPurpleAccent, // Custom color
barWidth: 3, // Thinner line
dotData: const FlDotData(
show: true), // Show dots on each point
belowBarData: BarAreaData(
// Add gradient fill below the line
show: true,
color: AppColor.deepPurpleAccent,
),
isStrokeJoinRound: true,
shadow: const BoxShadow(
color: AppColor.yellowColor,
blurRadius: 4,
offset: Offset(2, 2),
),
),
],
showingTooltipIndicators: const [],
titlesData: FlTitlesData(
show: true,
topTitles: AxisTitles(
axisNameWidget: Text(
'Days',
style: AppStyle.title,
),
axisNameSize: 30,
sideTitles: const SideTitles(
reservedSize: 30, showTitles: true)),
bottomTitles: AxisTitles(
axisNameWidget: Text(
'Total Trips on month'.tr,
style: AppStyle.title,
),
axisNameSize: 30,
sideTitles: const SideTitles(
reservedSize: 30, showTitles: true)),
leftTitles: AxisTitles(
axisNameWidget: Text(
'Counts of Trips on month'.tr,
style: AppStyle.title,
),
axisNameSize: 30,
sideTitles: const SideTitles(
reservedSize: 30, showTitles: true)),
),
gridData: const FlGridData(
show: true,
),
borderData: FlBorderData(
show: true,
border: const Border(
bottom: BorderSide(color: AppColor.accentColor),
left: BorderSide(color: AppColor.accentColor),
),
),
),
),
),
// SizedBox(
// height: Get.height * .4,
// child: PieChart(
// PieChartData(
// sectionsSpace: 4, // Adjust spacing between sections
// centerSpaceRadius:
// 40, // Adjust radius of center space
// sections: [
// for (final rideData in rideAdminController.rideData)
// PieChartSectionData(
// value: rideData.ridesCount.toDouble(),
// title: '${rideData.day}', showTitle: true,
// titleStyle:
// AppStyle.subtitle, // Display day as title
// radius: 60, // Adjust radius of each section
// color:
// AppColor.deepPurpleAccent, // Custom color
// ),
// ],
// ),
// ),
// ),
// SizedBox( return Scaffold(
// // height: 400, backgroundColor: cs.surface,
// child: SfCartesianChart( body: Column(
// legend: const Legend( children: [
// isVisible: true, _buildAppBar(context, cs),
// position: LegendPosition.bottom, Expanded(
// overflowMode: LegendItemOverflowMode.wrap, child: GetBuilder<RideAdminController>(
// textStyle: TextStyle( builder: (controller) {
// color: Colors.white, if (controller.isLoading) {
// fontSize: 12, return _buildLoadingState(cs);
// fontWeight: FontWeight.bold, }
// ),
// ),
// borderWidth: 2,
// borderColor: AppColor.blueColor,
// plotAreaBorderColor: AppColor.deepPurpleAccent,
// enableAxisAnimation: true,
// primaryXAxis: CategoryAxis(
// borderColor: AppColor.accentColor, borderWidth: 2,
// title: AxisTitle(
// text: 'Total Trips on month'.tr,
// textStyle: AppStyle.title,
// ),
// // labelRotation: 45,
// majorGridLines: const MajorGridLines(width: 0),
// ),
// primaryYAxis: const NumericAxis(isVisible: false),
// series: <LineSeries<ChartDataS, String>>[
// LineSeries<ChartDataS, String>(
// dataSource: rideAdminController.chartDatasync,
// xValueMapper: (ChartDataS data, _) => '${data.day}',
// yValueMapper: (ChartDataS data, _) =>
// data.ridesCount,
// dataLabelSettings:
// const DataLabelSettings(isVisible: true),
// ),
// ],
// ),
// ),
const SizedBox( return SingleChildScrollView(
height: 20, physics: const BouncingScrollPhysics(),
), padding: const EdgeInsets.all(16),
Card( child: Column(
elevation: 4, crossAxisAlignment: CrossAxisAlignment.start,
color: AppColor.deepPurpleAccent, children: [
child: Padding( _buildChartSection(controller, cs),
padding: const EdgeInsets.all(8.0), const SizedBox(height: 16),
child: Text( _buildMonthlyTotal(controller, cs),
'Total Trips on this Month is ${rideAdminController.jsonResponse['message'][0]['current_month_rides_count']}', const SizedBox(height: 16),
style: AppStyle.title, _buildRideDetails(controller, cs),
), ],
),
);
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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.map_rounded, color: cs.primary, size: 18),
),
const SizedBox(width: 10),
Text(
'شاشة الرحلات',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل الرحلات...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildChartSection(RideAdminController controller, ColorScheme cs) {
return Container(
height: 280,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.primary.withValues(alpha: 0.15)),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
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.trending_up_rounded,
color: cs.primary, size: 16),
),
const SizedBox(width: 10),
Text(
'رحلات الشهر',
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 16),
Expanded(
child: Directionality(
textDirection: TextDirection.ltr,
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
spots: controller.chartData,
isCurved: true,
color: cs.primary,
barWidth: 3,
dotData: const FlDotData(show: true),
belowBarData: BarAreaData(
show: true,
color: cs.primary.withValues(alpha: 0.08),
), ),
isStrokeJoinRound: true,
), ),
const SizedBox(
height: 20,
),
Card(
elevation: 4,
color: AppColor.yellowColor,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'Driver Average Duration: ${rideAdminController.ridesDetails[0]['driver_avg_duration']}',
style: AppStyle.subtitle,
),
Text(
'Number of Drivers: ${rideAdminController.ridesDetails[0]['num_Driver']}',
style: AppStyle.subtitle,
),
Text(
'Total Rides: ${rideAdminController.ridesDetails[0]['total_rides']}',
style: AppStyle.subtitle,
),
Text(
'Ongoing Rides: ${rideAdminController.ridesDetails[0]['ongoing_rides']}',
style: AppStyle.subtitle,
),
Text(
'Completed Rides: ${rideAdminController.ridesDetails[0]['completed_rides']}',
style: AppStyle.subtitle,
),
Text(
'Cancelled Rides: ${rideAdminController.ridesDetails[0]['cancelled_rides']}',
style: AppStyle.subtitle,
),
Text(
'Longest Duration: ${rideAdminController.ridesDetails[0]['longest_duration']}',
style: AppStyle.subtitle,
),
Text(
'Total Distance: ${rideAdminController.ridesDetails[0]['total_distance']} km',
style: AppStyle.subtitle,
),
Text(
'Average Distance: ${rideAdminController.ridesDetails[0]['average_distance']} km',
style: AppStyle.subtitle,
),
Text(
'Longest Distance: ${rideAdminController.ridesDetails[0]['longest_distance']} km',
style: AppStyle.subtitle,
),
Text(
'Total Driver Earnings: \$${rideAdminController.ridesDetails[0]['total_driver_earnings']}',
style: AppStyle.subtitle,
),
Text(
'Total Company Earnings: \$${rideAdminController.ridesDetails[0]['total_company_earnings']}',
style: AppStyle.subtitle,
),
Text(
'Company Percentage: ${rideAdminController.ridesDetails[0]['companyPercent']} %',
style: AppStyle.subtitle,
),
],
),
),
)
], ],
)) titlesData: FlTitlesData(
]); show: true,
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 24,
getTitlesWidget: (v, _) => Text(
'${v.toInt()}',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 9),
),
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 36,
getTitlesWidget: (v, _) => Text(
v.toInt().toString(),
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 9),
),
),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (_) => FlLine(
color: cs.outline.withValues(alpha: 0.5),
strokeWidth: 1),
),
borderData: FlBorderData(show: false),
),
),
),
),
],
),
);
}
Widget _buildMonthlyTotal(RideAdminController controller, ColorScheme cs) {
final count = controller.jsonResponse['message']?[0]
['current_month_rides_count'] ??
'0';
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.primary.withValues(alpha: 0.15)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
cs.primary.withValues(alpha: 0.20),
cs.primary.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.primary.withValues(alpha: 0.25)),
),
child: Icon(Icons.calendar_today_rounded,
color: cs.primary, size: 24),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'إجمالي رحلات الشهر',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 4),
Text(
'$count',
style: TextStyle(
color: cs.onSurface,
fontSize: 28,
fontWeight: FontWeight.w800,
height: 1,
),
),
],
),
],
),
);
}
Widget _buildRideDetails(RideAdminController controller, ColorScheme cs) {
if (controller.ridesDetails.isEmpty) return const SizedBox.shrink();
final details = controller.ridesDetails[0];
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: Icon(Icons.analytics_rounded,
color: const Color(0xFFF59E0B), size: 16),
),
const SizedBox(width: 10),
Text(
'تفاصيل الرحلات',
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 16),
_buildDetailGrid(details, cs),
],
),
);
}
Widget _buildDetailGrid(dynamic details, ColorScheme cs) {
final items = [
_DetailItem('متوسط مدة السائق', details['driver_avg_duration'] ?? '',
Icons.timer_rounded, cs.tertiary),
_DetailItem('عدد السائقين', details['num_Driver'] ?? '',
Icons.people_rounded, cs.primary),
_DetailItem('إجمالي الرحلات', details['total_rides'] ?? '',
Icons.route_rounded, cs.primary),
_DetailItem('رحلات نشطة', details['ongoing_rides'] ?? '',
Icons.play_circle_rounded, const Color(0xFFF59E0B)),
_DetailItem('مكتملة', details['completed_rides'] ?? '',
Icons.check_circle_rounded, const Color(0xFF10B981)),
_DetailItem('ملغاة', details['cancelled_rides'] ?? '',
Icons.cancel_rounded, cs.error),
_DetailItem('أطول مدة', details['longest_duration'] ?? '',
Icons.hourglass_top_rounded, cs.tertiary),
_DetailItem('إجمالي المسافة', '${details['total_distance'] ?? 0} km',
Icons.straighten_rounded, cs.primary),
_DetailItem('متوسط المسافة', '${details['average_distance'] ?? 0} km',
Icons.speed_rounded, const Color(0xFFF59E0B)),
_DetailItem('أطول مسافة', '${details['longest_distance'] ?? 0} km',
Icons.arrow_forward_rounded, cs.tertiary),
_DetailItem('إجمالي أرباح السائقين',
'\$${details['total_driver_earnings'] ?? 0}', Icons.payments_rounded,
const Color(0xFF10B981)),
_DetailItem('إجمالي أرباح الشركة',
'\$${details['total_company_earnings'] ?? 0}',
Icons.business_center_rounded, cs.primary),
_DetailItem('نسبة الشركة', '${details['companyPercent'] ?? 0}%',
Icons.pie_chart_rounded, const Color(0xFFF59E0B)),
];
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 2.2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: item.color.withValues(alpha: 0.15)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: item.color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(8),
),
child: Icon(item.icon, color: item.color, size: 16),
),
const SizedBox(width: 8),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.label,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
item.value,
style: TextStyle(
color: cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.w700,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
},
);
} }
} }
class _DetailItem {
final String label, value;
final IconData icon;
final Color color;
_DetailItem(this.label, this.value, this.icon, this.color);
}
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/security_v2_controller.dart'; import 'package:siro_admin/controller/admin/security_v2_controller.dart';
class AuditLogsPage extends StatelessWidget { class AuditLogsPage extends StatelessWidget {
@@ -8,21 +7,24 @@ class AuditLogsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text('سجل العمليات (Audit Logs)', title: Text('سجل العمليات (Audit Logs)',
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold, color: cs.onSurface)),
backgroundColor: AppColor.surface, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: cs.onSurface),
), ),
body: GetBuilder<SecurityV2Controller>( body: GetBuilder<SecurityV2Controller>(
init: SecurityV2Controller(), init: SecurityV2Controller(),
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.auditLogs.isEmpty) { if (ctrl.auditLogs.isEmpty) {
@@ -31,15 +33,15 @@ class AuditLogsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.security_rounded, Icon(Icons.security_rounded,
size: 64, color: AppColor.textSecondary.withOpacity(0.3)), size: 64, color: cs.onSurfaceVariant.withValues(alpha: 0.3)),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('لا توجد عمليات مسجلة حالياً', Text('لا توجد عمليات مسجلة حالياً',
style: TextStyle(color: AppColor.textSecondary)), style: TextStyle(color: cs.onSurfaceVariant)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'تأكد من إنشاء جدول سجل العمليات في قاعدة البيانات', 'تأكد من إنشاء جدول سجل العمليات في قاعدة البيانات',
style: TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 10)), color: cs.onSurfaceVariant, fontSize: 10)),
], ],
), ),
); );
@@ -50,7 +52,7 @@ class AuditLogsPage extends StatelessWidget {
itemCount: ctrl.auditLogs.length, itemCount: ctrl.auditLogs.length,
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final log = ctrl.auditLogs[i]; final log = ctrl.auditLogs[i];
return _buildLogItem(log); return _buildLogItem(log, cs);
}, },
); );
}, },
@@ -58,14 +60,14 @@ class AuditLogsPage extends StatelessWidget {
); );
} }
Widget _buildLogItem(Map<String, dynamic> log) { Widget _buildLogItem(Map<String, dynamic> log, ColorScheme cs) {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.divider), border: Border.all(color: cs.outline),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -74,22 +76,22 @@ class AuditLogsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(log['admin_name'] ?? 'أدمن غير معروف', Text(log['admin_name'] ?? 'أدمن غير معروف',
style: const TextStyle( style: TextStyle(
color: AppColor.accent, fontWeight: FontWeight.bold)), color: cs.primary, fontWeight: FontWeight.bold)),
Text(log['created_at'] ?? '', Text(log['created_at'] ?? '',
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 11)), color: cs.onSurfaceVariant, fontSize: 11)),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(log['action'] ?? '', Text(log['action'] ?? '',
style: const TextStyle( style: TextStyle(
color: AppColor.textPrimary, fontWeight: FontWeight.w600)), color: cs.onSurface, fontWeight: FontWeight.w600)),
if (log['details'] != null) ...[ if (log['details'] != null) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Text(log['details'], Text(log['details'],
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 12)), color: cs.onSurfaceVariant, fontSize: 12)),
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
@@ -97,19 +99,19 @@ class AuditLogsPage extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.info.withOpacity(0.1), color: cs.tertiary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text(log['table_name'] ?? '', child: Text(log['table_name'] ?? '',
style: const TextStyle( style: TextStyle(
color: AppColor.info, color: cs.tertiary,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('ID: ${log['record_id']}', Text('ID: ${log['record_id']}',
style: const TextStyle( style: TextStyle(
color: AppColor.textSecondary, fontSize: 10)), color: cs.onSurfaceVariant, fontSize: 10)),
], ],
), ),
], ],
@@ -3,23 +3,20 @@ import 'package:get/get.dart';
import '../../../controller/admin/staff_controller.dart'; import '../../../controller/admin/staff_controller.dart';
class AddStaffPage extends StatelessWidget { class AddStaffPage extends StatelessWidget {
final String role; // 'admin' or 'service' final String role;
const AddStaffPage({super.key, required this.role}); const AddStaffPage({super.key, required this.role});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.put(StaffController()); final controller = Get.put(StaffController());
controller.selectedRole = role; controller.selectedRole = role;
final cs = Theme.of(context).colorScheme;
const Color bgColor = Color(0xFF0D1117);
const Color inputColor = Color(0xFF161B22);
const Color accentColor = Color(0xFF00D4AA);
return Scaffold( return Scaffold(
backgroundColor: bgColor, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: Text(role == 'admin' ? "إضافة مدير جديد" : "إضافة موظف خدمة عملاء"), title: Text(role == 'admin' ? "إضافة مدير جديد" : "إضافة موظف خدمة عملاء"),
backgroundColor: bgColor, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
@@ -29,20 +26,20 @@ class AddStaffPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildSectionTitle("المعلومات الأساسية"), _buildSectionTitle("المعلومات الأساسية", cs),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
controller: controller.nameController, controller: controller.nameController,
label: "الاسم الكامل", label: "الاسم الكامل",
icon: Icons.person_outline, icon: Icons.person_outline,
fillColor: inputColor, cs: cs,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField( _buildTextField(
controller: controller.phoneController, controller: controller.phoneController,
label: "رقم الهاتف", label: "رقم الهاتف",
icon: Icons.phone_android_outlined, icon: Icons.phone_android_outlined,
fillColor: inputColor, cs: cs,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -50,7 +47,7 @@ class AddStaffPage extends StatelessWidget {
controller: controller.emailController, controller: controller.emailController,
label: "البريد الإلكتروني", label: "البريد الإلكتروني",
icon: Icons.email_outlined, icon: Icons.email_outlined,
fillColor: inputColor, cs: cs,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -58,11 +55,11 @@ class AddStaffPage extends StatelessWidget {
controller: controller.passwordController, controller: controller.passwordController,
label: "كلمة المرور", label: "كلمة المرور",
icon: Icons.lock_outline, icon: Icons.lock_outline,
fillColor: inputColor, cs: cs,
obscureText: true, obscureText: true,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildSectionTitle("معلومات إضافية"), _buildSectionTitle("معلومات إضافية", cs),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
@@ -72,7 +69,7 @@ class AddStaffPage extends StatelessWidget {
value: controller.selectedGender, value: controller.selectedGender,
items: ['Male', 'Female'], items: ['Male', 'Female'],
onChanged: (val) => controller.selectedGender = val!, onChanged: (val) => controller.selectedGender = val!,
fillColor: inputColor, cs: cs,
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
@@ -81,7 +78,7 @@ class AddStaffPage extends StatelessWidget {
controller: controller.birthdateController, controller: controller.birthdateController,
label: "تاريخ الميلاد", label: "تاريخ الميلاد",
icon: Icons.calendar_today_outlined, icon: Icons.calendar_today_outlined,
fillColor: inputColor, cs: cs,
hint: "YYYY-MM-DD", hint: "YYYY-MM-DD",
), ),
), ),
@@ -93,7 +90,7 @@ class AddStaffPage extends StatelessWidget {
value: controller.selectedCountry, value: controller.selectedCountry,
items: const ['Jordan', 'Syria', 'Egypt'], items: const ['Jordan', 'Syria', 'Egypt'],
onChanged: (val) => controller.selectedCountry = val!, onChanged: (val) => controller.selectedCountry = val!,
fillColor: inputColor, cs: cs,
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
GetBuilder<StaffController>( GetBuilder<StaffController>(
@@ -103,17 +100,17 @@ class AddStaffPage extends StatelessWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: controller.isLoading ? null : () => controller.registerStaff(), onPressed: controller.isLoading ? null : () => controller.registerStaff(),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: accentColor, backgroundColor: cs.primary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
), ),
child: controller.isLoading child: controller.isLoading
? const CircularProgressIndicator(color: Colors.white) ? CircularProgressIndicator(color: cs.onPrimary)
: Text( : Text(
"حفظ البيانات", "حفظ البيانات",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: bgColor, color: cs.onPrimary,
), ),
), ),
), ),
@@ -126,11 +123,11 @@ class AddStaffPage extends StatelessWidget {
); );
} }
Widget _buildSectionTitle(String title) { Widget _buildSectionTitle(String title, ColorScheme cs) {
return Text( return Text(
title, title,
style: const TextStyle( style: TextStyle(
color: Color(0xFF7D8590), color: cs.onSurfaceVariant,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.2, letterSpacing: 1.2,
@@ -142,28 +139,28 @@ class AddStaffPage extends StatelessWidget {
required TextEditingController controller, required TextEditingController controller,
required String label, required String label,
required IconData icon, required IconData icon,
required Color fillColor, required ColorScheme cs,
String? hint, String? hint,
bool obscureText = false, bool obscureText = false,
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
}) { }) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: fillColor, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
), ),
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
obscureText: obscureText, obscureText: obscureText,
keyboardType: keyboardType, keyboardType: keyboardType,
style: const TextStyle(color: Colors.white), style: TextStyle(color: cs.onSurface),
decoration: InputDecoration( decoration: InputDecoration(
labelText: label, labelText: label,
hintText: hint, hintText: hint,
hintStyle: const TextStyle(color: Colors.white24), hintStyle: TextStyle(color: cs.onSurface.withValues(alpha: 0.24)),
labelStyle: const TextStyle(color: Colors.white54), labelStyle: TextStyle(color: cs.onSurfaceVariant),
prefixIcon: Icon(icon, color: Colors.white38), prefixIcon: Icon(icon, color: cs.onSurfaceVariant),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
@@ -177,22 +174,22 @@ class AddStaffPage extends StatelessWidget {
required String value, required String value,
required List<String> items, required List<String> items,
required Function(String?) onChanged, required Function(String?) onChanged,
required Color fillColor, required ColorScheme cs,
}) { }) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: fillColor, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)), border: Border.all(color: cs.outline.withValues(alpha: 0.1)),
), ),
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
initialValue: value, initialValue: value,
dropdownColor: fillColor, dropdownColor: cs.surfaceContainerHighest,
style: const TextStyle(color: Colors.white), style: TextStyle(color: cs.onSurface),
decoration: InputDecoration( decoration: InputDecoration(
labelText: label, labelText: label,
labelStyle: const TextStyle(color: Colors.white54), labelStyle: TextStyle(color: cs.onSurfaceVariant),
border: InputBorder.none, border: InputBorder.none,
), ),
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(), items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
@@ -50,7 +50,7 @@ class _PendingAdminsPageState extends State<PendingAdminsPage> {
); );
if (response != 'failure') { if (response != 'failure') {
mySnackbarSuccess('تم تنفيذ الإجراء بنجاح'); mySnackbarSuccess('تم تنفيذ الإجراء بنجاح');
_fetchPendingAdmins(); // تحديث القائمة _fetchPendingAdmins();
} }
} catch (e) { } catch (e) {
mySnackbarError('حدث خطأ: $e'); mySnackbarError('حدث خطأ: $e');
@@ -59,35 +59,37 @@ class _PendingAdminsPageState extends State<PendingAdminsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFF0A0D14), backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
title: const Text('طلبات الانضمام المعلقة', style: TextStyle(fontWeight: FontWeight.bold)), title: const Text('طلبات الانضمام المعلقة', style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: const Color(0xFF161D2E), backgroundColor: cs.surfaceContainerHighest,
elevation: 0, elevation: 0,
), ),
body: _isLoading body: _isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFF00E5FF))) ? Center(child: CircularProgressIndicator(color: cs.primary))
: _pendingAdmins.isEmpty : _pendingAdmins.isEmpty
? _buildEmptyState() ? _buildEmptyState(cs)
: _buildList(), : _buildList(cs),
); );
} }
Widget _buildEmptyState() { Widget _buildEmptyState(ColorScheme cs) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.person_search_rounded, size: 80, color: Colors.grey[800]), Icon(Icons.person_search_rounded, size: 80, color: cs.outline),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: Colors.grey)), Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: cs.onSurfaceVariant)),
], ],
), ),
); );
} }
Widget _buildList() { Widget _buildList(ColorScheme cs) {
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemCount: _pendingAdmins.length, itemCount: _pendingAdmins.length,
@@ -97,40 +99,40 @@ class _PendingAdminsPageState extends State<PendingAdminsPage> {
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: const Color(0xFF161D2E), color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF1F2D4A)), border: Border.all(color: cs.outline),
), ),
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: const CircleAvatar( leading: CircleAvatar(
backgroundColor: Color(0xFF1F2D4A), backgroundColor: cs.outline,
child: Icon(Icons.person, color: Color(0xFF00E5FF)), child: Icon(Icons.person, color: cs.primary),
), ),
title: Text(admin['name'] ?? 'بدون اسم', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), title: Text(admin['name'] ?? 'بدون اسم', style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold)),
subtitle: Text(admin['phone'] ?? 'بدون رقم', style: const TextStyle(color: Colors.grey)), subtitle: Text(admin['phone'] ?? 'بدون رقم', style: TextStyle(color: cs.onSurfaceVariant)),
trailing: Text( trailing: Text(
admin['created_at']?.split(' ')[0] ?? '', admin['created_at']?.split(' ')[0] ?? '',
style: const TextStyle(color: Colors.grey, fontSize: 12), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
), ),
), ),
const Divider(color: Color(0xFF1F2D4A), height: 24), Divider(color: cs.outline, height: 24),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
TextButton( TextButton(
onPressed: () => _handleAction(admin['id'], 'rejected'), onPressed: () => _handleAction(admin['id'], 'rejected'),
style: TextButton.styleFrom(foregroundColor: Colors.redAccent), style: TextButton.styleFrom(foregroundColor: cs.error),
child: const Text('رفض'), child: const Text('رفض'),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: () => _handleAction(admin['id'], 'approved'), onPressed: () => _handleAction(admin['id'], 'approved'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00E5FF), backgroundColor: cs.primary,
foregroundColor: Colors.black, foregroundColor: cs.onPrimary,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@@ -1,7 +1,6 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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:siro_admin/controller/admin/analytics_v2_controller.dart';
class AdvancedAnalyticsPage extends StatelessWidget { class AdvancedAnalyticsPage extends StatelessWidget {
@@ -10,108 +9,250 @@ class AdvancedAnalyticsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.put(AnalyticsV2Controller()); final controller = Get.put(AnalyticsV2Controller());
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( body: Column(
title: const Text('التحليلات المتقدمة', children: [
style: TextStyle(fontWeight: FontWeight.bold)), _buildAppBar(context, cs),
backgroundColor: AppColor.surface, Expanded(
elevation: 0, child: GetBuilder<AnalyticsV2Controller>(
centerTitle: true, builder: (ctrl) {
actions: [ if (ctrl.isLoading) {
IconButton( return _buildLoadingState(cs);
icon: const Icon(Icons.refresh_rounded), }
onPressed: () => controller.fetchAllAnalytics(),
) return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSummarySection(ctrl.revenueData['summary'], cs),
const SizedBox(height: 24),
_buildSectionTitle('إيرادات آخر 30 يوم', cs),
_buildRevenueChart(ctrl.revenueData['daily'] ?? [], cs),
const SizedBox(height: 32),
_buildSectionTitle('نمو المستخدمين (آخر 30 يوم)', cs),
_buildGrowthChart(ctrl.growthData, cs),
const SizedBox(height: 32),
_buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)', cs),
_buildTopDriversList(ctrl.topDrivers, cs),
const SizedBox(height: 40),
],
),
);
},
),
),
], ],
), ),
body: GetBuilder<AnalyticsV2Controller>( );
builder: (ctrl) { }
if (ctrl.isLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColor.accent));
}
return SingleChildScrollView( Widget _buildAppBar(BuildContext context, ColorScheme cs) {
padding: const EdgeInsets.all(16), return Container(
child: Column( padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
crossAxisAlignment: CrossAxisAlignment.start, decoration: BoxDecoration(
children: [ color: cs.surface,
_buildSummarySection(ctrl.revenueData['summary']), border: Border(bottom: BorderSide(color: cs.outline)),
const SizedBox(height: 24), ),
_buildSectionTitle('إيرادات آخر 30 يوم'), child: Row(
_buildRevenueChart(ctrl.revenueData['daily'] ?? []), children: [
const SizedBox(height: 32), GestureDetector(
_buildSectionTitle('نمو المستخدمين (آخر 30 يوم)'), onTap: () => Get.back(),
_buildGrowthChart(ctrl.growthData), child: Container(
const SizedBox(height: 32), padding: const EdgeInsets.all(8),
_buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)'), decoration: BoxDecoration(
_buildTopDriversList(ctrl.topDrivers), color: cs.surfaceContainerHighest,
const SizedBox(height: 40), 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.analytics_rounded,
color: cs.primary, size: 18),
),
const SizedBox(width: 10),
Text(
'التحليلات المتقدمة',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const Spacer(),
GestureDetector(
onTap: () => Get.find<AnalyticsV2Controller>().fetchAllAnalytics(),
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: 18),
),
),
],
), ),
); );
} }
Widget _buildSectionTitle(String title) { Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل التحليلات...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildSectionTitle(String title, ColorScheme cs) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 16),
child: Text( child: Row(
title, children: [
style: const TextStyle( Container(
color: AppColor.textPrimary, width: 3,
fontSize: 16, height: 16,
fontWeight: FontWeight.bold, decoration: BoxDecoration(
), color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 10),
Text(
title,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
), ),
); );
} }
Widget _buildSummarySection(Map<String, dynamic>? summary) { Widget _buildSummarySection(
Map<String, dynamic>? summary, ColorScheme cs) {
if (summary == null) return const SizedBox(); if (summary == null) return const SizedBox();
return Row( return Row(
children: [ children: [
_buildSummaryCard('إجمالي الإيرادات', _buildSummaryCard(
'${summary['total_revenue_all'] ?? 0}', AppColor.info), 'إجمالي الإيرادات',
'${summary['total_revenue_all'] ?? 0}',
cs.tertiary,
Icons.attach_money_rounded,
cs,
),
const SizedBox(width: 12), const SizedBox(width: 12),
_buildSummaryCard('صافي الربح', '${summary['total_profit_all'] ?? 0}', _buildSummaryCard(
AppColor.success), 'صافي الربح',
'${summary['total_profit_all'] ?? 0}',
const Color(0xFF10B981),
Icons.trending_up_rounded,
cs,
),
], ],
); );
} }
Widget _buildSummaryCard(String title, String value, Color color) { Widget _buildSummaryCard(
String title, String value, Color color, IconData icon, ColorScheme cs) {
return Expanded( return Expanded(
child: Container( child: Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3)), border: Border.all(color: color.withValues(alpha: 0.2)),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(title, Container(
style: const TextStyle( padding: const EdgeInsets.all(8),
color: AppColor.textSecondary, fontSize: 12)), decoration: BoxDecoration(
const SizedBox(height: 8), color: color.withValues(alpha: 0.12),
Text(value, borderRadius: BorderRadius.circular(10),
style: TextStyle( border: Border.all(color: color.withValues(alpha: 0.25)),
color: color, fontSize: 22, fontWeight: FontWeight.bold)), ),
child: Icon(icon, color: color, size: 18),
),
const SizedBox(height: 12),
Text(
title,
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 6),
Text(
value,
style: TextStyle(
color: color,
fontSize: 22,
fontWeight: FontWeight.w800,
height: 1,
),
),
], ],
), ),
), ),
); );
} }
Widget _buildRevenueChart(List<dynamic> daily) { Widget _buildRevenueChart(List<dynamic> daily, ColorScheme cs) {
if (daily.isEmpty) return const Center(child: Text('لا توجد بيانات')); if (daily.isEmpty) {
return Container(
height: 200,
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
),
child: Center(
child: Text('لا توجد بيانات',
style: TextStyle(color: cs.onSurfaceVariant)),
),
);
}
List<FlSpot> revenueSpots = []; List<FlSpot> revenueSpots = [];
List<FlSpot> profitSpots = []; List<FlSpot> profitSpots = [];
@@ -127,71 +268,131 @@ class AdvancedAnalyticsPage extends StatelessWidget {
height: 300, height: 300,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
), ),
child: LineChart( child: Column(
LineChartData( crossAxisAlignment: CrossAxisAlignment.start,
gridData: FlGridData( children: [
show: true, Row(
drawVerticalLine: false, children: [
getDrawingHorizontalLine: (v) => _LegendDot(color: cs.tertiary, label: 'الإيرادات'),
FlLine(color: AppColor.divider, strokeWidth: 1)), const SizedBox(width: 16),
titlesData: FlTitlesData( _LegendDot(color: const Color(0xFF10B981), label: 'الربح'),
show: true, ],
rightTitles: ),
const AxisTitles(sideTitles: SideTitles(showTitles: false)), const SizedBox(height: 16),
topTitles: Expanded(
const AxisTitles(sideTitles: SideTitles(showTitles: false)), child: Directionality(
bottomTitles: AxisTitles( textDirection: TextDirection.ltr,
sideTitles: SideTitles( child: LineChart(
showTitles: true, LineChartData(
getTitlesWidget: (val, meta) { gridData: FlGridData(
if (val.toInt() % 7 == 0 && val.toInt() < daily.length) { show: true,
return Text( drawVerticalLine: false,
daily[val.toInt()]['date'].toString().substring(8), getDrawingHorizontalLine: (v) => FlLine(
style: const TextStyle( color: cs.outline.withValues(alpha: 0.5),
color: AppColor.textSecondary, fontSize: 10)); strokeWidth: 1),
} ),
return const SizedBox(); titlesData: FlTitlesData(
}, show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (v, _) => Text(
v >= 1000 ? '${(v / 1000).toStringAsFixed(0)}k' : v.toInt().toString(),
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 9),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 24,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 &&
val.toInt() < daily.length) {
return Text(
daily[val.toInt()]['date']
.toString()
.substring(8),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 9),
);
}
return const SizedBox();
},
),
),
),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: revenueSpots,
isCurved: true,
color: cs.tertiary,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: cs.tertiary.withValues(alpha: 0.08),
),
),
LineChartBarData(
spots: profitSpots,
isCurved: true,
color: const Color(0xFF10B981),
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: const Color(0xFF10B981).withValues(alpha: 0.08),
),
),
],
),
), ),
), ),
), ),
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) { Widget _buildGrowthChart(Map<String, dynamic> data, ColorScheme cs) {
final passengers = data['passenger_daily'] as List<dynamic>? ?? []; final passengers = data['passenger_daily'] as List<dynamic>? ?? [];
final drivers = data['driver_daily'] as List<dynamic>? ?? []; final drivers = data['driver_daily'] as List<dynamic>? ?? [];
if (passengers.isEmpty && drivers.isEmpty) { if (passengers.isEmpty && drivers.isEmpty) {
return const Center(child: Text('لا توجد بيانات')); return Container(
height: 200,
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
),
child: Center(
child: Text('لا توجد بيانات',
style: TextStyle(color: cs.onSurfaceVariant)),
),
);
} }
List<BarChartGroupData> barGroups = []; List<BarChartGroupData> barGroups = [];
@@ -211,59 +412,118 @@ class AdvancedAnalyticsPage extends StatelessWidget {
x: i, x: i,
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
toY: pCount, toY: pCount,
color: AppColor.info, color: cs.tertiary.withValues(alpha: 0.7),
width: 8, width: 8,
borderRadius: BorderRadius.circular(4)), borderRadius: BorderRadius.circular(4),
),
BarChartRodData( BarChartRodData(
toY: dCount, toY: dCount,
color: AppColor.warning, color: const Color(0xFFF59E0B).withValues(alpha: 0.7),
width: 8, width: 8,
borderRadius: BorderRadius.circular(4)), borderRadius: BorderRadius.circular(4),
),
], ],
), ),
); );
} }
return Container( return Container(
height: 250, height: 280,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
), ),
child: BarChart( child: Column(
BarChartData( crossAxisAlignment: CrossAxisAlignment.start,
barGroups: barGroups, children: [
borderData: FlBorderData(show: false), Row(
titlesData: FlTitlesData( children: [
show: true, _LegendDot(color: cs.tertiary, label: 'ركاب جدد'),
rightTitles: const SizedBox(width: 16),
const AxisTitles(sideTitles: SideTitles(showTitles: false)), _LegendDot(
topTitles: color: const Color(0xFFF59E0B), label: 'سائقين جدد'),
const AxisTitles(sideTitles: SideTitles(showTitles: false)), ],
bottomTitles: AxisTitles( ),
sideTitles: SideTitles( const SizedBox(height: 16),
showTitles: true, Expanded(
getTitlesWidget: (val, meta) { child: Directionality(
if (val.toInt() % 7 == 0 && val.toInt() < passengers.length) { textDirection: TextDirection.ltr,
return Text( child: BarChart(
passengers[val.toInt()]['date'].toString().substring(8), BarChartData(
style: const TextStyle( barGroups: barGroups,
color: AppColor.textSecondary, fontSize: 10)); borderData: FlBorderData(show: false),
} titlesData: FlTitlesData(
return const SizedBox(); show: true,
}, rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (v, _) => Text(
v.toInt().toString(),
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 9),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 24,
getTitlesWidget: (val, meta) {
if (val.toInt() % 7 == 0 &&
val.toInt() < passengers.length) {
return Text(
passengers[val.toInt()]['date']
.toString()
.substring(8),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 9),
);
}
return const SizedBox();
},
),
),
),
),
), ),
), ),
), ),
), ],
), ),
); );
} }
Widget _buildTopDriversList(List<dynamic> drivers) { Widget _buildTopDriversList(List<dynamic> drivers, ColorScheme cs) {
if (drivers.isEmpty) return const Center(child: Text('لا توجد بيانات')); if (drivers.isEmpty) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
),
child: Center(
child: Text('لا توجد بيانات',
style: TextStyle(color: cs.onSurfaceVariant)),
),
);
}
return ListView.builder( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
@@ -271,47 +531,105 @@ class AdvancedAnalyticsPage extends StatelessWidget {
itemCount: drivers.length, itemCount: drivers.length,
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final d = drivers[i]; final d = drivers[i];
final isTop3 = i < 3;
final rankColors = [
const Color(0xFFF59E0B),
cs.onSurfaceVariant,
const Color(0xFFCD7F32),
];
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.surface, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isTop3
? rankColors[i].withValues(alpha: 0.2)
: cs.outline,
),
), ),
child: Row( child: Row(
children: [ children: [
CircleAvatar( Container(
backgroundColor: AppColor.accent.withOpacity(0.1), width: 40,
child: Text('${i + 1}', height: 40,
style: const TextStyle( decoration: BoxDecoration(
color: AppColor.accent, fontWeight: FontWeight.bold)), color: isTop3
? rankColors[i].withValues(alpha: 0.12)
: cs.outline,
shape: BoxShape.circle,
border: Border.all(
color: isTop3
? rankColors[i].withValues(alpha: 0.3)
: Colors.transparent,
),
),
child: Center(
child: Text(
'${i + 1}',
style: TextStyle(
color: isTop3 ? rankColors[i] : cs.onSurfaceVariant,
fontWeight: FontWeight.w800,
fontSize: 14,
),
),
),
), ),
const SizedBox(width: 16), const SizedBox(width: 14),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('${d['first_name']} ${d['last_name']}', Text(
style: const TextStyle( '${d['first_name']} ${d['last_name']}',
color: AppColor.textPrimary, style: TextStyle(
fontWeight: FontWeight.bold)), color: cs.onSurface,
Text(d['phone'] ?? '', fontWeight: FontWeight.w600,
style: const TextStyle( fontSize: 14,
color: AppColor.textSecondary, fontSize: 12)), ),
),
const SizedBox(height: 3),
Text(
d['phone'] ?? '',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontFamily: 'monospace',
),
),
], ],
), ),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text('${d['completed_rides']} رحلة', Container(
style: const TextStyle( padding: const EdgeInsets.symmetric(
color: AppColor.info, fontWeight: FontWeight.bold)), horizontal: 8, vertical: 3),
Text('${d['total_revenue']} ل.س', decoration: BoxDecoration(
style: const TextStyle( color: cs.tertiary.withValues(alpha: 0.1),
color: AppColor.success, borderRadius: BorderRadius.circular(8),
fontSize: 12, ),
fontWeight: FontWeight.bold)), child: Text(
'${d['completed_rides']} رحلة',
style: TextStyle(
color: cs.tertiary,
fontWeight: FontWeight.w700,
fontSize: 12,
),
),
),
const SizedBox(height: 4),
Text(
'${d['total_revenue']} ل.س',
style: TextStyle(
color: const Color(0xFF10B981),
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
], ],
), ),
], ],
@@ -321,3 +639,36 @@ class AdvancedAnalyticsPage extends StatelessWidget {
); );
} }
} }
class _LegendDot extends StatelessWidget {
final Color color;
final String label;
const _LegendDot({required this.color, required this.label});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w500,
),
),
],
);
}
}
@@ -3,7 +3,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:intl/intl.dart' hide TextDirection; import 'package:intl/intl.dart' hide TextDirection;
import 'package:siro_admin/constant/theme.dart';
import 'package:siro_admin/controller/admin/static_controller.dart'; import 'package:siro_admin/controller/admin/static_controller.dart';
import 'notes_driver_page.dart'; import 'notes_driver_page.dart';
+240 -66
View File
@@ -1,81 +1,255 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/style.dart';
import 'package:siro_admin/views/widgets/elevated_btn.dart';
import 'package:siro_admin/views/widgets/mycircular.dart';
import '../../../controller/admin/wallet_admin_controller.dart'; import '../../../controller/admin/wallet_admin_controller.dart';
import '../../widgets/my_scafold.dart'; import '../../widgets/mycircular.dart';
class Wallet extends StatelessWidget { class Wallet extends StatelessWidget {
Wallet({super.key}); Wallet({super.key});
final WalletAdminController walletAdminController = final WalletAdminController walletAdminController =
Get.put(WalletAdminController()); Get.put(WalletAdminController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyScafolld( final cs = Theme.of(context).colorScheme;
title: 'Wallet'.tr,
body: [
GetBuilder<WalletAdminController>(builder: (walletAdminController) {
return Center(
child: walletAdminController.isLoading
? const MyCircularProgressIndicator()
: Column(
children: [
MyElevatedButton(
title: 'Pay to them to banks'.tr,
onPressed: () async {
await walletAdminController.payToBankDriverAll();
}),
SizedBox(
height: Get.height * .8,
child: ListView.builder(
itemCount:
walletAdminController.driversWalletPoints.length,
itemBuilder: (BuildContext context, int index) {
var res = walletAdminController
.driversWalletPoints[index];
if (res != null && res['name_arabic'] != null) { return Scaffold(
return Padding( backgroundColor: cs.surface,
padding: const EdgeInsets.all(4.0), body: Column(
child: Container( children: [
decoration: AppStyle.boxDecoration1, _buildAppBar(context, cs),
child: Padding( Expanded(
padding: const EdgeInsets.symmetric( child: GetBuilder<WalletAdminController>(
horizontal: 10), builder: (controller) {
child: Row( if (controller.isLoading) {
mainAxisAlignment: return _buildLoadingState(cs);
MainAxisAlignment.spaceBetween, }
children: [
Text( if (controller.driversWalletPoints.isEmpty) {
'driver name: ${res['name_arabic'].toString()}'), return _buildEmptyState(cs);
Text( }
'Amount: ${res['total_amount'].toString()}'),
], return Column(
), children: [
), _buildPayAllButton(context, controller, cs),
), Expanded(
); child: ListView.separated(
} else { padding: const EdgeInsets.fromLTRB(16, 8, 16, 80),
return Container(); // Return an empty container if the data is null physics: const BouncingScrollPhysics(),
} itemCount: controller.driversWalletPoints.length,
}, separatorBuilder: (_, __) => const SizedBox(height: 10),
), itemBuilder: (context, index) {
) var res = controller.driversWalletPoints[index];
], if (res != null && res['name_arabic'] != null) {
return _buildWalletCard(res, cs);
}
return const SizedBox.shrink();
},
),
),
],
);
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
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.account_balance_wallet_rounded,
color: cs.primary, size: 18),
),
const SizedBox(width: 10),
Text(
'المحافظ المالية',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const Spacer(),
GestureDetector(
onTap: () async {
walletAdminController.getWalletForEachDriverToPay();
},
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: 18),
),
),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return const Center(child: MyCircularProgressIndicator());
}
Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.account_balance_wallet_rounded,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا توجد محافظ مالية',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildPayAllButton(
BuildContext context, WalletAdminController controller, ColorScheme cs) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
child: SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
icon: const Icon(Icons.send_rounded, color: Colors.white, size: 18),
label: Text(
'دفع الجميع للبنوك',
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
),
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14)),
elevation: 0,
),
onPressed: () async {
await controller.payToBankDriverAll();
},
),
),
);
}
Widget _buildWalletCard(dynamic res, ColorScheme cs) {
final String name = res['name_arabic']?.toString() ?? 'بدون اسم';
final String amount = res['total_amount']?.toString() ?? '0';
return Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF10B981).withValues(alpha: 0.20),
const Color(0xFF10B981).withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)),
),
child: Icon(Icons.person_rounded, color: const Color(0xFF10B981), size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.w600,
fontSize: 14,
),
), ),
); const SizedBox(height: 3),
}) Text(
], 'سائق',
isleading: true, style: TextStyle(
action: IconButton( color: cs.onSurfaceVariant,
onPressed: () async { fontSize: 11,
walletAdminController.getWalletForEachDriverToPay(); ),
}, ),
icon: const Icon( ],
Icons.refresh, ),
color: Colors.black, ),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amount,
style: TextStyle(
color: const Color(0xFF10B981),
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
Text(
'ل.س',
style: TextStyle(
color: const Color(0xFF10B981).withValues(alpha: 0.6),
fontSize: 10,
fontWeight: FontWeight.w500,
),
),
],
),
],
), ),
), ),
); );
@@ -6,9 +6,6 @@ import '../../print.dart';
import '../widgets/snackbar.dart'; import '../widgets/snackbar.dart';
import 'add_invoice_page.dart'; import 'add_invoice_page.dart';
// نفترض أن هذا الموديل موجود في مشروعك، إذا لم يكن موجوداً يرجى إضافته أو تعديل الاستيراد
// import '../../model/invoice_model.dart';
class InvoiceListPage extends StatefulWidget { class InvoiceListPage extends StatefulWidget {
const InvoiceListPage({super.key}); const InvoiceListPage({super.key});
@@ -17,17 +14,11 @@ class InvoiceListPage extends StatefulWidget {
} }
class _InvoiceListPageState extends State<InvoiceListPage> { class _InvoiceListPageState extends State<InvoiceListPage> {
List<dynamic> invoices = []; // استخدام dynamic لتجنب مشاكل الموديل إذا اختلف List<dynamic> invoices = [];
int totalCount = 0; int totalCount = 0;
double totalAmount = 0.0; double totalAmount = 0.0;
bool isLoading = true; bool isLoading = true;
// الألوان "الإيجابية" للتصميم الجديد
final Color primaryColor = const Color(0xFF4F46E5); // Indigo
final Color secondaryColor = const Color(0xFF818CF8); // Lighter Indigo
final Color moneyColor = const Color(0xFF059669); // Emerald Green
final Color bgColor = const Color(0xFFF3F4F6); // Light Gray Background
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -46,7 +37,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
final data = response; final data = response;
if (mounted) { if (mounted) {
setState(() { setState(() {
invoices = data['data']; // استخدام البيانات مباشرة invoices = data['data'];
totalCount = int.tryParse(data['summary']['count'].toString()) ?? 0; totalCount = int.tryParse(data['summary']['count'].toString()) ?? 0;
totalAmount = totalAmount =
double.tryParse(data['summary']['total'].toString()) ?? 0.0; double.tryParse(data['summary']['total'].toString()) ?? 0.0;
@@ -66,6 +57,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
} }
void _showImageDialog(BuildContext context, String imageUrl) { void _showImageDialog(BuildContext context, String imageUrl) {
final cs = Theme.of(context).colorScheme;
showDialog( showDialog(
context: context, context: context,
builder: (_) => Dialog( builder: (_) => Dialog(
@@ -75,8 +67,9 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
children: [ children: [
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
), ),
padding: const EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: ClipRRect( child: ClipRRect(
@@ -93,16 +86,16 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
height: 200, height: 200,
width: 200, width: 200,
child: Center( child: Center(
child: CircularProgressIndicator(color: primaryColor), child: CircularProgressIndicator(color: cs.primary),
), ),
); );
}, },
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return const SizedBox( return SizedBox(
height: 150, height: 150,
width: 150, width: 150,
child: Icon(Icons.broken_image, child: Icon(Icons.broken_image_rounded,
size: 60, color: Colors.grey), size: 60, color: cs.onSurfaceVariant),
); );
}, },
), ),
@@ -112,11 +105,17 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
Positioned( Positioned(
top: 0, top: 0,
right: 0, right: 0,
child: CircleAvatar( child: GestureDetector(
backgroundColor: Colors.white, onTap: () => Navigator.pop(context),
child: IconButton( child: Container(
icon: const Icon(Icons.close, color: Colors.black), padding: const EdgeInsets.all(6),
onPressed: () => Navigator.pop(context), decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
border: Border.all(color: cs.outline),
),
child: Icon(Icons.close_rounded,
color: cs.onSurfaceVariant, size: 18),
), ),
), ),
), ),
@@ -128,17 +127,18 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: bgColor, backgroundColor: cs.surface,
// زر عائم بتصميم متدرج
floatingActionButton: Container( floatingActionButton: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(colors: [primaryColor, secondaryColor]), gradient: LinearGradient(colors: [cs.primary, cs.primary.withValues(alpha: 0.7)]),
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: primaryColor.withOpacity(0.4), color: cs.primary.withValues(alpha: 0.3),
blurRadius: 10, blurRadius: 12,
offset: const Offset(0, 4)) offset: const Offset(0, 4))
], ],
), ),
@@ -146,32 +146,29 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
onPressed: () => Get.to(() => AddInvoicePage()), onPressed: () => Get.to(() => AddInvoicePage()),
label: const Text('إضافة فاتورة', label: const Text('إضافة فاتورة',
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
icon: const Icon(Icons.add), icon: const Icon(Icons.add_rounded),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
elevation: 0, elevation: 0,
), ),
), ),
body: Column( body: Column(
children: [ children: [
// 1. رأس الصفحة (Header & Summary) _buildHeader(cs),
_buildHeader(),
// 2. قائمة الفواتير
Expanded( Expanded(
child: isLoading child: isLoading
? Center(child: CircularProgressIndicator(color: primaryColor)) ? _buildLoadingState(cs)
: invoices.isEmpty : invoices.isEmpty
? _buildEmptyState() ? _buildEmptyState(cs)
: RefreshIndicator( : RefreshIndicator(
onRefresh: fetchInvoices, onRefresh: fetchInvoices,
color: primaryColor, color: cs.primary,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
itemCount: invoices.length, itemCount: invoices.length,
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final invoice = invoices[index]; final invoice = invoices[index];
return _buildInvoiceCard(invoice); return _buildInvoiceCard(invoice, cs);
}, },
), ),
), ),
@@ -181,13 +178,12 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
); );
} }
// === تصميم الهيدر (رأس الصفحة) === Widget _buildHeader(ColorScheme cs) {
Widget _buildHeader() {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 20, top: MediaQuery.of(context).padding.top + 20,
bottom: 30, bottom: 24,
left: 20, left: 20,
right: 20, right: 20,
), ),
@@ -195,15 +191,15 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [primaryColor, const Color(0xFF6366F1)], colors: [cs.primary, cs.primary.withValues(alpha: 0.7)],
), ),
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(30), bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(30), bottomRight: Radius.circular(24),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: primaryColor.withOpacity(0.3), color: cs.primary.withValues(alpha: 0.2),
blurRadius: 20, blurRadius: 20,
offset: const Offset(0, 10), offset: const Offset(0, 10),
), ),
@@ -211,38 +207,41 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
), ),
child: Column( child: Column(
children: [ children: [
// العنوان وزر الرجوع
Row( Row(
children: [ children: [
IconButton( GestureDetector(
icon: const Icon(Icons.arrow_back_ios, onTap: () => Get.back(),
color: Colors.white, size: 20), child: Container(
onPressed: () => Get.back(), padding: const EdgeInsets.all(8),
), decoration: BoxDecoration(
const Expanded( color: Colors.white.withValues(alpha: 0.2),
child: Text( borderRadius: BorderRadius.circular(10),
"سجل الفواتير",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
), ),
child: const Icon(Icons.arrow_back_ios_new_rounded,
color: Colors.white, size: 16),
), ),
), ),
const SizedBox(width: 40), // للمحاذاة const Spacer(),
const Text(
"سجل الفواتير",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const Spacer(),
const SizedBox(width: 34),
], ],
), ),
const SizedBox(height: 25), const SizedBox(height: 20),
// بطاقات الملخص
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _buildSummaryItem( child: _buildSummaryItem(
title: "الإجمالي", title: "الإجمالي",
value: "${totalAmount.toStringAsFixed(1)} د.أ", value: "${totalAmount.toStringAsFixed(1)} د.أ",
icon: Icons.attach_money, icon: Icons.attach_money_rounded,
isMoney: true, isMoney: true,
), ),
), ),
@@ -251,7 +250,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
child: _buildSummaryItem( child: _buildSummaryItem(
title: "عدد الفواتير", title: "عدد الفواتير",
value: "$totalCount", value: "$totalCount",
icon: Icons.receipt_long, icon: Icons.receipt_long_rounded,
isMoney: false, isMoney: false,
), ),
), ),
@@ -262,17 +261,18 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
); );
} }
Widget _buildSummaryItem( Widget _buildSummaryItem({
{required String title, required String title,
required String value, required String value,
required IconData icon, required IconData icon,
required bool isMoney}) { required bool isMoney,
}) {
return Column( return Column(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2), color: Colors.white.withValues(alpha: 0.2),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(icon, color: Colors.white, size: 20), child: Icon(icon, color: Colors.white, size: 20),
@@ -282,21 +282,70 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
value, value,
style: TextStyle( style: TextStyle(
color: isMoney ? const Color(0xFFD1FAE5) : Colors.white, color: isMoney ? const Color(0xFFD1FAE5) : Colors.white,
fontSize: 22, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w800,
), ),
), ),
Text( Text(
title, title,
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 12), style: const TextStyle(
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500),
), ),
], ],
); );
} }
// === تصميم بطاقة الفاتورة === Widget _buildLoadingState(ColorScheme cs) {
Widget _buildInvoiceCard(dynamic invoice) { return Center(
// استخراج البيانات بأمان child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل الفواتير...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.receipt_long_rounded,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text("لا توجد فواتير حالياً",
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)),
const SizedBox(height: 8),
TextButton.icon(
onPressed: fetchInvoices,
icon: const Icon(Icons.refresh_rounded),
label: const Text("تحديث"),
),
],
),
);
}
Widget _buildInvoiceCard(dynamic invoice, ColorScheme cs) {
String name = invoice['name'] ?? 'بدون اسم'; String name = invoice['name'] ?? 'بدون اسم';
String amount = invoice['amount']?.toString() ?? '0'; String amount = invoice['amount']?.toString() ?? '0';
String date = invoice['date'] ?? ''; String date = invoice['date'] ?? '';
@@ -304,22 +353,16 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
String? imageUrl = invoice['imageLink']; String? imageUrl = invoice['imageLink'];
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(16),
boxShadow: [ border: Border.all(color: cs.outline),
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(16),
onTap: () { onTap: () {
if (imageUrl != null && imageUrl.isNotEmpty) { if (imageUrl != null && imageUrl.isNotEmpty) {
_showImageDialog(context, imageUrl); _showImageDialog(context, imageUrl);
@@ -328,96 +371,93 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
} }
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(14),
child: Row( child: Row(
children: [ children: [
// 1. أيقونة أو صورة مصغرة
Container( Container(
width: 50, width: 48,
height: 50, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor.withOpacity(0.08), color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.primary.withValues(alpha: 0.15)),
), ),
child: imageUrl != null && imageUrl.isNotEmpty child: imageUrl != null && imageUrl.isNotEmpty
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(14),
child: Image.network( child: Image.network(
imageUrl, imageUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => errorBuilder: (_, __, ___) =>
Icon(Icons.receipt, color: primaryColor), Icon(Icons.receipt_rounded, color: cs.primary, size: 22),
), ),
) )
: Icon(Icons.receipt_outlined, color: primaryColor), : Icon(Icons.receipt_outlined, color: cs.primary, size: 22),
), ),
const SizedBox(width: 14),
const SizedBox(width: 16),
// 2. التفاصيل
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Expanded(
name, child: Text(
style: const TextStyle( name,
fontWeight: FontWeight.bold, style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600,
color: Color(0xFF1F2937), fontSize: 15,
color: cs.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4), horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: cs.surface,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: cs.outline),
), ),
child: Text( child: Text(
"#$invNumber", "#$invNumber",
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: Colors.grey[600], color: cs.onSurfaceVariant,
fontWeight: FontWeight.bold), fontWeight: FontWeight.w600),
), ),
), ),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 4),
Text( Text(
date, date,
style: TextStyle(color: Colors.grey[500], fontSize: 12), style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 11),
), ),
], ],
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
// 3. المبلغ
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(
amount, amount,
style: TextStyle( style: TextStyle(
color: moneyColor, color: const Color(0xFF10B981),
fontWeight: FontWeight.w900, fontWeight: FontWeight.w800,
fontSize: 18, fontSize: 18,
), ),
), ),
Text( Text(
"د.أ", "د.أ",
style: TextStyle( style: TextStyle(
color: moneyColor.withOpacity(0.7), color: const Color(0xFF10B981).withValues(alpha: 0.6),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 12, fontSize: 10,
), ),
), ),
], ],
@@ -429,26 +469,4 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
), ),
); );
} }
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long_rounded, size: 80, color: Colors.grey[300]),
const SizedBox(height: 16),
Text(
"لا توجد فواتير حالياً",
style: TextStyle(color: Colors.grey[500], fontSize: 16),
),
const SizedBox(height: 8),
TextButton.icon(
onPressed: fetchInvoices,
icon: const Icon(Icons.refresh),
label: const Text("تحديث"),
),
],
),
);
}
} }
+32 -30
View File
@@ -1,8 +1,6 @@
// org_list_page.dart — قائمة مؤسسات مواصلاتي (لوحة إدارة سيرو)
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../controller/transit/transit_admin_controller.dart'; import '../../controller/transit/transit_admin_controller.dart';
import '../../controller/transit/transit_admin_models.dart'; import '../../controller/transit/transit_admin_models.dart';
import 'org_create_page.dart'; import 'org_create_page.dart';
@@ -27,22 +25,25 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GetBuilder<TransitAdminController>( return GetBuilder<TransitAdminController>(
builder: (c) => Scaffold( builder: (c) => Scaffold(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColor.bg, backgroundColor: cs.surface,
elevation: 0, elevation: 0,
title: const Text('مواصلاتي — المؤسسات', style: TextStyle(color: AppColor.textPrimary)), title: Text('مواصلاتي — المؤسسات',
style: TextStyle(color: cs.onSurface)),
iconTheme: IconThemeData(color: cs.onSurface),
actions: [ actions: [
// زر اعتماد الخطوط
IconButton( IconButton(
icon: const Icon(Icons.route_rounded, color: AppColor.warning), icon: Icon(Icons.route_rounded, color: cs.tertiary),
tooltip: 'اعتماد الخطوط', tooltip: 'اعتماد الخطوط',
onPressed: () => Get.to(() => const RouteApprovalPage()), onPressed: () => Get.to(() => const RouteApprovalPage()),
), ),
IconButton( IconButton(
icon: const Icon(Icons.add, color: AppColor.accent), icon: Icon(Icons.add, color: cs.primary),
onPressed: () async { onPressed: () async {
final created = await Get.to(() => const TransitOrgCreatePage()); final created = await Get.to(() => const TransitOrgCreatePage());
if (created == true) c.fetchOrgs(); if (created == true) c.fetchOrgs();
@@ -56,17 +57,17 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: TextField( child: TextField(
controller: _searchCtrl, controller: _searchCtrl,
style: const TextStyle(color: AppColor.textPrimary), style: TextStyle(color: cs.onSurface),
onSubmitted: (v) { onSubmitted: (v) {
c.searchQuery = v; c.searchQuery = v;
c.fetchOrgs(); c.fetchOrgs();
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'ابحث عن مؤسسة...', hintText: 'ابحث عن مؤسسة...',
hintStyle: const TextStyle(color: AppColor.textSecondary), hintStyle: TextStyle(color: cs.onSurfaceVariant),
prefixIcon: const Icon(Icons.search, color: AppColor.textSecondary), prefixIcon: Icon(Icons.search, color: cs.onSurfaceVariant),
filled: true, filled: true,
fillColor: AppColor.surface, fillColor: cs.surfaceContainerHighest,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -76,17 +77,18 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
), ),
Expanded( Expanded(
child: c.isLoadingList child: c.isLoadingList
? const Center(child: CircularProgressIndicator(color: AppColor.accent)) ? Center(child: CircularProgressIndicator(color: cs.primary))
: c.orgs.isEmpty : c.orgs.isEmpty
? const Center( ? Center(
child: Text('لا توجد مؤسسات', style: TextStyle(color: AppColor.textSecondary)), child: Text('لا توجد مؤسسات',
style: TextStyle(color: cs.onSurfaceVariant)),
) )
: RefreshIndicator( : RefreshIndicator(
onRefresh: c.fetchOrgs, onRefresh: c.fetchOrgs,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
itemCount: c.orgs.length, itemCount: c.orgs.length,
itemBuilder: (_, i) => _orgCard(c.orgs[i]), itemBuilder: (_, i) => _orgCard(c.orgs[i], cs),
), ),
), ),
), ),
@@ -96,30 +98,30 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
); );
} }
Widget _orgCard(TransitOrgSummary org) { Widget _orgCard(TransitOrgSummary org, ColorScheme cs) {
Color statusColor; Color statusColor;
switch (org.contractStatus) { switch (org.contractStatus) {
case 'active': case 'active':
statusColor = AppColor.success; statusColor = const Color(0xFF10B981);
break; break;
case 'trial': case 'trial':
statusColor = AppColor.info; statusColor = cs.tertiary;
break; break;
case 'suspended': case 'suspended':
statusColor = AppColor.warning; statusColor = cs.tertiary;
break; break;
default: default:
statusColor = AppColor.danger; statusColor = cs.error;
} }
return Card( return Card(
color: AppColor.surface, color: cs.surfaceContainerHighest,
margin: const EdgeInsets.only(bottom: 10), margin: const EdgeInsets.only(bottom: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
title: Text(org.nameAr, title: Text(org.nameAr,
style: const TextStyle(color: AppColor.textPrimary, fontWeight: FontWeight.bold)), style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold)),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 6), padding: const EdgeInsets.only(top: 6),
child: Row( child: Row(
@@ -127,7 +129,7 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: statusColor.withOpacity(0.15), color: statusColor.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text(org.contractStatus, child: Text(org.contractStatus,
@@ -135,21 +137,21 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('${org.city} · ${org.country}', Text('${org.city} · ${org.country}',
style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)),
const Spacer(), const Spacer(),
Icon(Icons.directions_bus, size: 14, color: AppColor.textSecondary), Icon(Icons.directions_bus, size: 14, color: cs.onSurfaceVariant),
const SizedBox(width: 2), const SizedBox(width: 2),
Text('${org.vehiclesCount}', Text('${org.vehiclesCount}',
style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(width: 10), const SizedBox(width: 10),
Icon(Icons.people, size: 14, color: AppColor.textSecondary), Icon(Icons.people, size: 14, color: cs.onSurfaceVariant),
const SizedBox(width: 2), const SizedBox(width: 2),
Text('${org.activeEnrollments}', Text('${org.activeEnrollments}',
style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)),
], ],
), ),
), ),
trailing: const Icon(Icons.arrow_forward_ios, size: 14, color: AppColor.textSecondary), trailing: Icon(Icons.arrow_forward_ios, size: 14, color: cs.onSurfaceVariant),
onTap: () => Get.to(() => TransitOrgDetailsPage(orgId: org.id, orgName: org.nameAr)), onTap: () => Get.to(() => TransitOrgDetailsPage(orgId: org.id, orgName: org.nameAr)),
), ),
); );
@@ -1,13 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:siro_admin/constant/theme.dart';
import 'package:siro_admin/main.dart'; import 'package:siro_admin/main.dart';
import 'package:siro_admin/constant/box_name.dart'; import 'package:siro_admin/constant/box_name.dart';
import 'package:siro_admin/views/admin/captain/captain.dart'; import 'package:siro_admin/views/admin/captain/captain.dart';
import 'package:siro_admin/views/admin/passenger/passenger.dart'; import 'package:siro_admin/views/admin/passenger/passenger.dart';
import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart'; import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart';
import 'package:siro_admin/views/transit/org_list_page.dart'; import 'package:siro_admin/views/transit/org_list_page.dart';
import 'package:siro_admin/views/admin/rides/rides.dart';
import 'package:siro_admin/views/admin/static/static.dart'; import 'package:siro_admin/views/admin/static/static.dart';
import 'package:siro_admin/controller/admin/static_controller.dart'; import 'package:siro_admin/controller/admin/static_controller.dart';
import 'package:siro_admin/views/admin/drivers/monitor_ride.dart'; import 'package:siro_admin/views/admin/drivers/monitor_ride.dart';