إصلاح توافق فلاتر مع Theme الجديد
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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("تحديث"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user