Update: 2026-05-07 03:06:15
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../core/network/dio_client.dart';
|
||||
import '../../../core/utils/app_snackbar.dart';
|
||||
import '../../../core/utils/logger.dart';
|
||||
import 'subscription_controller.dart';
|
||||
|
||||
class PaymentReceiptController extends GetxController {
|
||||
var payment = {}.obs;
|
||||
var isUploading = false.obs;
|
||||
final referenceController = TextEditingController();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments != null) {
|
||||
payment.value = Get.arguments;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
referenceController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> submitReference() async {
|
||||
final ref = referenceController.text.trim();
|
||||
if (ref.isEmpty) {
|
||||
AppSnackbar.showWarning('تنبيه', 'الرجاء إدخال رقم المرجع أولاً');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isUploading.value = true;
|
||||
String paymentId = payment['id'];
|
||||
|
||||
final res = await DioClient().client.post(
|
||||
'payments/verify-reference',
|
||||
data: {
|
||||
'payment_id': paymentId,
|
||||
'bank_reference': ref,
|
||||
},
|
||||
);
|
||||
|
||||
if (res.data['success'] == true) {
|
||||
final data = res.data['data'];
|
||||
|
||||
// Refresh subscription info
|
||||
if (Get.isRegistered<SubscriptionController>()) {
|
||||
Get.find<SubscriptionController>().loadAll();
|
||||
}
|
||||
|
||||
Get.back(); // close the screen
|
||||
|
||||
if (data['status'] == 'approved') {
|
||||
AppSnackbar.showSuccess('مبروك!', data['message']);
|
||||
} else {
|
||||
AppSnackbar.showInfo('تم الحفظ', data['message']);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to submit reference', e);
|
||||
AppSnackbar.showError('خطأ', 'فشل التحقق من رقم المرجع. تأكد من صحته أو حاول لاحقاً.');
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../../core/network/dio_client.dart';
|
||||
import '../../../core/utils/logger.dart';
|
||||
|
||||
class SubscriptionController extends GetxController {
|
||||
var plans = <Map<String, dynamic>>[].obs;
|
||||
var currentSubscription = Rxn<Map<String, dynamic>>();
|
||||
var myPayments = <Map<String, dynamic>>[].obs;
|
||||
var isLoading = true.obs;
|
||||
var isCreatingPayment = false.obs;
|
||||
var activePaymentRequest = Rxn<Map<String, dynamic>>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
loadAll();
|
||||
}
|
||||
|
||||
Future<void> loadAll() async {
|
||||
isLoading.value = true;
|
||||
await Future.wait([
|
||||
loadPlans(),
|
||||
loadCurrentSubscription(),
|
||||
loadMyPayments(),
|
||||
]);
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
Future<void> loadPlans() async {
|
||||
try {
|
||||
final res = await DioClient().client.get('subscriptions/plans');
|
||||
if (res.data['success'] == true && res.data['data'] != null) {
|
||||
plans.value = List<Map<String, dynamic>>.from(res.data['data']);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to load plans', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadCurrentSubscription() async {
|
||||
try {
|
||||
final res = await DioClient().client.get('subscriptions/current');
|
||||
if (res.data['success'] == true && res.data['data'] != null) {
|
||||
currentSubscription.value = Map<String, dynamic>.from(res.data['data']);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to load subscription', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadMyPayments() async {
|
||||
try {
|
||||
final res = await DioClient().client.get('payments/my-requests');
|
||||
if (res.data['success'] == true && res.data['data'] != null) {
|
||||
myPayments.value = List<Map<String, dynamic>>.from(res.data['data']);
|
||||
// Check for active pending payment
|
||||
final pending = myPayments.firstWhereOrNull((p) => p['status'] == 'pending');
|
||||
activePaymentRequest.value = pending;
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to load my payments', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> createPaymentRequest(String planId) async {
|
||||
try {
|
||||
isCreatingPayment.value = true;
|
||||
final res = await DioClient().client.post('payments/create', data: {'plan_id': planId});
|
||||
if (res.data['success'] == true && res.data['data'] != null) {
|
||||
final result = Map<String, dynamic>.from(res.data['data']);
|
||||
activePaymentRequest.value = result;
|
||||
await loadMyPayments();
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to create payment', e);
|
||||
} finally {
|
||||
isCreatingPayment.value = false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/payment_receipt_controller.dart';
|
||||
|
||||
class PaymentReceiptView extends StatelessWidget {
|
||||
const PaymentReceiptView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(PaymentReceiptController());
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA),
|
||||
appBar: AppBar(
|
||||
title: const Text('إتمام الدفع', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
backgroundColor: isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Payment Info Card
|
||||
Obx(() => Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text('تفاصيل التحويل المطلوب', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('الاسم المستعار (CliQ)', controller.payment['cliq_alias'] ?? '', isDark, isHighlight: true),
|
||||
const Divider(height: 24),
|
||||
_buildInfoRow('المبلغ المطلوب', '${controller.payment['amount_jod'] ?? 0} JOD', isDark),
|
||||
],
|
||||
),
|
||||
)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
Text('الخطوة التالية:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: isDark ? Colors.white : Colors.black87)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'قم بتحويل المبلغ عبر تطبيق البنك الخاص بك إلى الاسم المستعار المذكور أعلاه (CliQ). بعد إتمام الحوالة بنجاح، ستصلك رسالة أو إشعار من البنك يحتوي على "رقم المرجع" للعملية. انسخه والصقه هنا.',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.grey.shade700, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Reference Number Input Area
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('رقم المرجع (Reference Number)', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: controller.referenceController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'مثال: 1234567890',
|
||||
hintStyle: TextStyle(color: isDark ? Colors.white38 : Colors.grey),
|
||||
filled: true,
|
||||
fillColor: isDark ? const Color(0xFF1A1A2E) : const Color(0xFFF1F5F9),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Submit Button
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: Obx(() => ElevatedButton.icon(
|
||||
onPressed: controller.isUploading.value
|
||||
? null
|
||||
: () => controller.submitReference(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
disabledBackgroundColor: isDark ? Colors.white10 : Colors.grey.shade300,
|
||||
),
|
||||
icon: controller.isUploading.value
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.check_circle_outline),
|
||||
label: Text(
|
||||
controller.isUploading.value ? 'جاري التحقق...' : 'تأكيد الدفع',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value, bool isDark, {bool isHighlight = false}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.grey.shade600)),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: isHighlight ? 12 : 0, vertical: isHighlight ? 6 : 0),
|
||||
decoration: isHighlight ? BoxDecoration(
|
||||
color: const Color(0xFF0F4C81).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
) : null,
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
color: isHighlight ? const Color(0xFF0F4C81) : (isDark ? Colors.white : Colors.black87),
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/subscription_controller.dart';
|
||||
import '../../../core/utils/app_snackbar.dart';
|
||||
|
||||
class SubscriptionView extends StatelessWidget {
|
||||
const SubscriptionView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(SubscriptionController());
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA),
|
||||
appBar: AppBar(
|
||||
title: const Text('الاشتراكات', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
backgroundColor: isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator(color: Color(0xFF0F4C81)));
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => controller.loadAll(),
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Current Subscription Status
|
||||
if (controller.currentSubscription.value != null)
|
||||
_buildCurrentPlan(controller.currentSubscription.value!, isDark),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Active Payment Request Banner
|
||||
if (controller.activePaymentRequest.value != null)
|
||||
_buildActivePaymentBanner(controller.activePaymentRequest.value!, isDark),
|
||||
|
||||
// Plans Header
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.diamond_rounded, color: Color(0xFFD4AF37), size: 22),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'اختر باقتك',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'ادفع عبر CliQ — بدون عمولة!',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Plans Grid
|
||||
...controller.plans.map((plan) => _buildPlanCard(plan, controller, isDark)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Payment History
|
||||
if (controller.myPayments.isNotEmpty) ...[
|
||||
const Text('سجل المدفوعات', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 12),
|
||||
...controller.myPayments.map((p) => _buildPaymentHistoryItem(p, isDark)),
|
||||
],
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCurrentPlan(Map<String, dynamic> sub, bool isDark) {
|
||||
final planName = sub['plan_name'] ?? sub['plan_id'] ?? 'مجانية';
|
||||
final daysLeft = sub['days_remaining'] ?? 0;
|
||||
final used = sub['invoices_used'] ?? 0;
|
||||
final limit = sub['invoices_limit'] ?? 0;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF0F4C81), Color(0xFF1A6BB5)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.verified, color: Color(0xFFD4AF37), size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'باقتك الحالية: $planName',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
_buildSubInfoChip(Icons.timer_outlined, '$daysLeft يوم متبقي'),
|
||||
const SizedBox(width: 12),
|
||||
_buildSubInfoChip(Icons.receipt_long, '$used/$limit فاتورة'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubInfoChip(IconData icon, String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: Colors.white70, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(text, style: const TextStyle(color: Colors.white, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivePaymentBanner(Map<String, dynamic> payment, bool isDark) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF7ED),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFFED7AA)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.pending_actions, color: Color(0xFFF59E0B), size: 20),
|
||||
SizedBox(width: 8),
|
||||
Text('لديك طلب دفع قائم', style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF92400E))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('رقم المرجع: ${payment['reference_number']}', style: const TextStyle(fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
Text('المبلغ: ${payment['amount_jod']} JOD', style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.upload_file, size: 18),
|
||||
label: const Text('رفع وصل الدفع'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFF59E0B),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
// Navigate to receipt upload screen
|
||||
Get.toNamed('/payment-receipt', arguments: payment);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlanCard(Map<String, dynamic> plan, SubscriptionController ctrl, bool isDark) {
|
||||
final isPopular = plan['is_popular'] == true;
|
||||
final price = (plan['price_jod'] ?? 0).toString();
|
||||
final features = (plan['features'] as List?)?.cast<String>() ?? [];
|
||||
final nameAr = plan['name_ar'] ?? plan['name_en'] ?? 'باقة';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isPopular ? const Color(0xFFD4AF37) : (isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
width: isPopular ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Popular Badge
|
||||
if (isPopular)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD4AF37),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(14), topRight: Radius.circular(14)),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('⭐ الأكثر شعبية', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(nameAr, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: isDark ? Colors.white : const Color(0xFF0F172A))),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(text: price, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: isDark ? const Color(0xFF5EEAD4) : const Color(0xFF0F4C81))),
|
||||
TextSpan(text: ' JOD', style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
plan['description_ar'] ?? '',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Features
|
||||
...features.map((f) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Color(0xFF10B981), size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(f, style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.black87))),
|
||||
],
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Stats Row
|
||||
Row(
|
||||
children: [
|
||||
_buildPlanStat(Icons.business, '${plan['max_companies'] ?? 0} شركات'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPlanStat(Icons.receipt_long, '${plan['max_invoices_month'] ?? 0} فاتورة/شهر'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPlanStat(Icons.people, '${plan['max_users'] ?? 0} مستخدمين'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Upgrade Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: Obx(() => ElevatedButton(
|
||||
onPressed: ctrl.isCreatingPayment.value ? null : () async {
|
||||
final result = await ctrl.createPaymentRequest(plan['id'].toString());
|
||||
if (result != null) {
|
||||
AppSnackbar.showSuccess('تم إنشاء طلب الدفع', 'قم بالتحويل عبر CliQ ثم ارفع وصل الدفع');
|
||||
} else {
|
||||
AppSnackbar.showError('خطأ', 'فشل إنشاء طلب الدفع');
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isPopular ? const Color(0xFFD4AF37) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: ctrl.isCreatingPayment.value
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Text('ترقية الآن', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlanStat(IconData icon, String text) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F4C81).withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: const Color(0xFF0F4C81)),
|
||||
const SizedBox(height: 2),
|
||||
Text(text, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w600), textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentHistoryItem(Map<String, dynamic> payment, bool isDark) {
|
||||
final status = payment['status'] ?? '';
|
||||
Color statusColor;
|
||||
String statusText;
|
||||
|
||||
switch (status) {
|
||||
case 'approved': statusColor = const Color(0xFF10B981); statusText = 'تم الاعتماد'; break;
|
||||
case 'pending': statusColor = const Color(0xFFF59E0B); statusText = 'قيد الانتظار'; break;
|
||||
case 'uploaded': statusColor = const Color(0xFF3B82F6); statusText = 'تحت المراجعة'; break;
|
||||
case 'rejected': statusColor = const Color(0xFFEF4444); statusText = 'مرفوض'; break;
|
||||
default: statusColor = Colors.grey; statusText = status;
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
elevation: 0,
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(Icons.payment_rounded, color: statusColor, size: 20),
|
||||
),
|
||||
title: Text(payment['plan_name'] ?? 'باقة', style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
|
||||
subtitle: Text('${payment['amount_jod']} JOD • ${payment['reference_number']}', style: const TextStyle(fontSize: 11, fontFamily: 'monospace')),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(statusText, style: TextStyle(color: statusColor, fontSize: 11, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user