Update: 2026-05-08 01:15:44

This commit is contained in:
Hamza-Ayed
2026-05-08 01:15:44 +03:00
parent 1a6ed52a52
commit 928e8e27e3
10 changed files with 991 additions and 4 deletions

View File

@@ -21,6 +21,7 @@ import '../../features/onboarding/views/onboarding_view.dart';
import '../../features/companies/views/company_stats_view.dart';
import '../../features/users/views/users_management_view.dart';
import '../../features/tenants/views/tenants_management_view.dart';
import '../../features/reports/views/tax_report_view.dart';
part 'app_routes.dart';
@@ -151,5 +152,9 @@ class AppPages {
name: AppRoutes.USERS_MANAGEMENT,
page: () => const UsersManagementView(),
),
GetPage(
name: AppRoutes.TAX_REPORT,
page: () => const TaxReportView(),
),
];
}

View File

@@ -21,4 +21,5 @@ abstract class AppRoutes {
static const COMPANY_STATS = '/company-stats';
static const TENANTS_MANAGEMENT = '/tenants-management';
static const USERS_MANAGEMENT = '/users-management';
static const TAX_REPORT = '/tax-report';
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:dio/dio.dart';
import '../../../core/network/dio_client.dart';
import '../../../core/utils/app_snackbar.dart';
import '../../../core/utils/logger.dart';
@@ -7,6 +8,7 @@ import '../../../core/utils/logger.dart';
class InvoiceDetailController extends GetxController {
var invoice = {}.obs;
var isLoading = true.obs;
var isSaving = false.obs;
String? invoiceId;
@override
@@ -42,6 +44,25 @@ class InvoiceDetailController extends GetxController {
}
}
Future<void> updateInvoice(Map<String, dynamic> data) async {
try {
isSaving.value = true;
data['id'] = invoiceId;
final res = await DioClient().client.post('invoices/update', data: data);
if (res.data['success'] == true) {
AppSnackbar.showSuccess('تم الحفظ', 'تم تحديث بيانات الفاتورة');
await fetchInvoiceDetails();
} else {
AppSnackbar.showError('خطأ', res.data['message'] ?? 'فشل التحديث');
}
} catch (e) {
AppLogger.error('Failed to update invoice', e);
AppSnackbar.showError('خطأ', 'حدث خطأ أثناء التحديث');
} finally {
isSaving.value = false;
}
}
Future<void> approveInvoice() async {
try {
final res = await DioClient()
@@ -49,7 +70,6 @@ class InvoiceDetailController extends GetxController {
.post('invoices/approve', data: {'invoice_id': invoiceId});
if (res.data['success'] == true) {
AppSnackbar.showSuccess('تم الاعتماد', 'تم اعتماد الفاتورة بنجاح');
// Refresh the detail view
fetchInvoiceDetails();
} else {
AppSnackbar.showError('خطأ', 'فشل اعتماد الفاتورة');
@@ -63,7 +83,6 @@ class InvoiceDetailController extends GetxController {
void viewOriginalImage() {
final fileUrl = invoice['file_url'];
if (fileUrl != null && fileUrl.isNotEmpty) {
// Navigate to a dedicated image viewer or show in a dialog
final fullUrl = 'https://musadaq.intaleqapp.com/api$fileUrl';
Get.to(() => Scaffold(
appBar: AppBar(
@@ -90,8 +109,24 @@ class InvoiceDetailController extends GetxController {
}
}
Future<void> exportInvoices({String? companyId}) async {
try {
final cId = companyId ?? invoice['company_id'];
AppSnackbar.showInfo('جاري التصدير', 'يتم تحميل ملف الفواتير...');
final res = await DioClient().client.get(
'invoices/export',
queryParameters: {'company_id': cId},
options: Options(responseType: ResponseType.bytes),
);
// For now, just confirm download was successful
AppSnackbar.showSuccess('تم التصدير', 'تم تحميل ملف CSV بنجاح (${res.data.length} bytes)');
} catch (e) {
AppLogger.error('Failed to export', e);
AppSnackbar.showError('خطأ', 'فشل تصدير الفواتير');
}
}
Future<void> submitToJoFotara() async {
// Confirmation dialog
final confirmed = await Get.dialog<bool>(
AlertDialog(
title: const Text('تأكيد الإرسال'),
@@ -124,7 +159,7 @@ class InvoiceDetailController extends GetxController {
if (res.data['success'] == true) {
AppSnackbar.showSuccess('تم الإرسال', 'تم تقديم الفاتورة لجوفتورة بنجاح');
fetchInvoiceDetails(); // Refresh to show JoFotara status
fetchInvoiceDetails();
} else {
AppSnackbar.showError('خطأ', res.data['message'] ?? 'فشل الإرسال');
}

View File

@@ -79,6 +79,20 @@ class InvoiceDetailView extends StatelessWidget {
// ─── Action Buttons ───
if (status == 'extracted') ...[
SizedBox(
height: 52,
child: ElevatedButton.icon(
onPressed: () => _showEditDialog(context, inv, controller),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B82F6),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
icon: const Icon(Icons.edit_note_rounded),
label: const Text('تعديل بيانات الفاتورة', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
),
),
const SizedBox(height: 12),
SizedBox(
height: 52,
child: ElevatedButton.icon(
@@ -568,4 +582,128 @@ class InvoiceDetailView extends StatelessWidget {
if (num == num.truncateToDouble()) return num.toStringAsFixed(0);
return num.toStringAsFixed(3);
}
void _showEditDialog(BuildContext context, Map inv, InvoiceDetailController controller) {
final invNumC = TextEditingController(text: inv['invoice_number']?.toString() ?? '');
final invDateC = TextEditingController(text: inv['invoice_date']?.toString() ?? '');
final supplierNameC = TextEditingController(text: inv['supplier_name']?.toString() ?? '');
final supplierTinC = TextEditingController(text: inv['supplier_tin']?.toString() ?? '');
final supplierAddressC = TextEditingController(text: inv['supplier_address']?.toString() ?? '');
final buyerNameC = TextEditingController(text: inv['buyer_name']?.toString() ?? '');
final buyerTinC = TextEditingController(text: inv['buyer_tin']?.toString() ?? '');
final subtotalC = TextEditingController(text: inv['subtotal']?.toString() ?? '0');
final taxC = TextEditingController(text: inv['tax_amount']?.toString() ?? '0');
final discountC = TextEditingController(text: inv['discount_total']?.toString() ?? '0');
final grandC = TextEditingController(text: inv['grand_total']?.toString() ?? '0');
Get.to(() => Scaffold(
appBar: AppBar(
title: const Text('تعديل الفاتورة', style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: const Color(0xFF3B82F6),
foregroundColor: Colors.white,
actions: [
Obx(() => controller.isSaving.value
? const Padding(padding: EdgeInsets.all(16), child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)))
: IconButton(
icon: const Icon(Icons.save_rounded),
onPressed: () {
controller.updateInvoice({
'invoice_number': invNumC.text,
'invoice_date': invDateC.text,
'supplier_name': supplierNameC.text,
'supplier_tin': supplierTinC.text,
'supplier_address': supplierAddressC.text,
'buyer_name': buyerNameC.text,
'buyer_tin': buyerTinC.text,
'subtotal': subtotalC.text,
'tax_amount': taxC.text,
'discount_total': discountC.text,
'grand_total': grandC.text,
}).then((_) => Get.back());
},
),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('معلومات الفاتورة', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_editRow('رقم الفاتورة', invNumC, Icons.numbers),
_editRow('تاريخ الفاتورة', invDateC, Icons.calendar_today),
const Divider(height: 32),
const Text('بيانات المورّد', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_editRow('اسم المورّد', supplierNameC, Icons.store),
_editRow('الرقم الضريبي', supplierTinC, Icons.badge),
_editRow('العنوان', supplierAddressC, Icons.location_on),
const Divider(height: 32),
const Text('بيانات العميل', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_editRow('اسم العميل', buyerNameC, Icons.person),
_editRow('الرقم الضريبي للعميل', buyerTinC, Icons.badge),
const Divider(height: 32),
const Text('المبالغ', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_editRow('المبلغ قبل الضريبة', subtotalC, Icons.attach_money, isNumeric: true),
_editRow('الخصم', discountC, Icons.discount, isNumeric: true),
_editRow('الضريبة', taxC, Icons.percent, isNumeric: true),
_editRow('الإجمالي', grandC, Icons.payments, isNumeric: true),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton.icon(
onPressed: () {
controller.updateInvoice({
'invoice_number': invNumC.text,
'invoice_date': invDateC.text,
'supplier_name': supplierNameC.text,
'supplier_tin': supplierTinC.text,
'supplier_address': supplierAddressC.text,
'buyer_name': buyerNameC.text,
'buyer_tin': buyerTinC.text,
'subtotal': subtotalC.text,
'tax_amount': taxC.text,
'discount_total': discountC.text,
'grand_total': grandC.text,
}).then((_) => Get.back());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
icon: const Icon(Icons.save),
label: const Text('حفظ التعديلات', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
),
),
const SizedBox(height: 40),
],
),
),
));
}
Widget _editRow(String label, TextEditingController ctrl, IconData icon, {bool isNumeric = false}) {
return Padding(
padding: const EdgeInsets.only(bottom: 14),
child: TextField(
controller: ctrl,
textDirection: TextDirection.rtl,
keyboardType: isNumeric ? const TextInputType.numberWithOptions(decimal: true) : TextInputType.text,
decoration: InputDecoration(
labelText: label,
prefixIcon: Icon(icon, size: 20),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
filled: true,
fillColor: Colors.grey.shade50,
),
),
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:get/get.dart';
import '../../../core/network/dio_client.dart';
import '../../../core/utils/app_snackbar.dart';
import '../../../core/utils/logger.dart';
class TaxReportController extends GetxController {
var isLoading = true.obs;
var report = {}.obs;
var selectedMonth = DateTime.now().month.obs;
var selectedYear = DateTime.now().year.obs;
String? companyId;
@override
void onInit() {
super.onInit();
if (Get.arguments != null) {
companyId = Get.arguments['company_id'];
}
fetchReport();
}
Future<void> fetchReport() async {
try {
isLoading.value = true;
final params = <String, dynamic>{
'month': selectedMonth.value,
'year': selectedYear.value,
};
if (companyId != null) params['company_id'] = companyId;
final res = await DioClient().client.get('reports/tax-summary', queryParameters: params);
if (res.data['success'] == true) {
report.value = res.data['data'];
}
} catch (e) {
AppLogger.error('Failed to fetch tax report', e);
AppSnackbar.showError('خطأ', 'تعذر تحميل التقرير');
} finally {
isLoading.value = false;
}
}
void changeMonth(int delta) {
var m = selectedMonth.value + delta;
var y = selectedYear.value;
if (m > 12) { m = 1; y++; }
if (m < 1) { m = 12; y--; }
selectedMonth.value = m;
selectedYear.value = y;
fetchReport();
}
String get monthName {
const months = ['', 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];
return '${months[selectedMonth.value]} ${selectedYear.value}';
}
}

View File

@@ -0,0 +1,344 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/tax_report_controller.dart';
class TaxReportView extends StatelessWidget {
const TaxReportView({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(TaxReportController());
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(fontFamily: 'El Messiri', fontWeight: FontWeight.bold)),
centerTitle: true,
backgroundColor: const Color(0xFF0F4C81),
foregroundColor: Colors.white,
),
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator(color: Color(0xFF0F4C81)));
}
final summary = controller.report['summary'] as Map? ?? {};
final growth = controller.report['growth'] as Map? ?? {};
final topSuppliers = (controller.report['top_suppliers'] as List?) ?? [];
final daily = (controller.report['daily_breakdown'] as List?) ?? [];
return RefreshIndicator(
onRefresh: controller.fetchReport,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Month Selector
_buildMonthSelector(controller, isDark),
const SizedBox(height: 16),
// Summary Cards
Row(
children: [
Expanded(child: _buildMetricCard('إجمالي الفواتير', '${summary['total_invoices'] ?? 0}', Icons.receipt_long, const Color(0xFF3B82F6), growth['invoices'], isDark)),
const SizedBox(width: 12),
Expanded(child: _buildMetricCard('إجمالي المبيعات', '${_fmt(summary['total_grand'])} JOD', Icons.payments, const Color(0xFF10B981), growth['revenue'], isDark)),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildMetricCard('ضريبة المبيعات', '${_fmt(summary['total_tax'])} JOD', Icons.percent, const Color(0xFFF59E0B), growth['tax'], isDark)),
const SizedBox(width: 12),
Expanded(child: _buildMetricCard('مقدمة لجوفتورة', '${summary['submitted_count'] ?? 0}', Icons.send_rounded, const Color(0xFF6366F1), null, isDark)),
],
),
const SizedBox(height: 20),
// Status Breakdown
_buildStatusBreakdown(summary, isDark),
const SizedBox(height: 20),
// Daily Chart (simple bar representation)
if (daily.isNotEmpty) ...[
_buildDailyChart(daily, isDark),
const SizedBox(height: 20),
],
// Top Suppliers
if (topSuppliers.isNotEmpty) ...[
_buildTopSuppliers(topSuppliers, isDark),
const SizedBox(height: 20),
],
// Amount Details
_buildAmountDetails(summary, isDark),
const SizedBox(height: 40),
],
),
),
);
}),
);
}
Widget _buildMonthSelector(TaxReportController controller, bool isDark) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: () => controller.changeMonth(1),
),
Obx(() => Text(
controller.monthName,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold,
color: isDark ? Colors.white : const Color(0xFF0F172A),
),
)),
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: () => controller.changeMonth(-1),
),
],
),
);
}
Widget _buildMetricCard(String label, String value, IconData icon, Color color, dynamic growthPct, bool isDark) {
final growth = double.tryParse(growthPct?.toString() ?? '') ?? 0;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 18, color: color),
const Spacer(),
if (growthPct != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: (growth >= 0 ? const Color(0xFF10B981) : Colors.red).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${growth >= 0 ? "+" : ""}${growth.toStringAsFixed(1)}%',
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: growth >= 0 ? const Color(0xFF10B981) : Colors.red),
),
),
],
),
const SizedBox(height: 12),
Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: isDark ? Colors.white : const Color(0xFF0F172A), fontFamily: 'monospace')),
const SizedBox(height: 4),
Text(label, style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)),
],
),
);
}
Widget _buildStatusBreakdown(Map summary, bool isDark) {
final total = (int.tryParse(summary['total_invoices']?.toString() ?? '0') ?? 0);
final approved = (int.tryParse(summary['approved_count']?.toString() ?? '0') ?? 0);
final pending = (int.tryParse(summary['pending_count']?.toString() ?? '0') ?? 0);
final submitted = (int.tryParse(summary['submitted_count']?.toString() ?? '0') ?? 0);
return 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('توزيع الحالات', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_statusRow('معتمدة', approved, total, const Color(0xFF10B981), isDark),
const SizedBox(height: 8),
_statusRow('قيد المراجعة', pending, total, const Color(0xFFF59E0B), isDark),
const SizedBox(height: 8),
_statusRow('مقدمة لجوفتورة', submitted, total, const Color(0xFF6366F1), isDark),
],
),
);
}
Widget _statusRow(String label, int count, int total, Color color, bool isDark) {
final pct = total > 0 ? count / total : 0.0;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.black87)),
Text('$count (${(pct * 100).toStringAsFixed(0)}%)', style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: color)),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(value: pct, backgroundColor: color.withValues(alpha: 0.1), color: color, minHeight: 6),
),
],
);
}
Widget _buildDailyChart(List daily, bool isDark) {
final maxVal = daily.fold<double>(0, (max, d) => (double.tryParse(d['daily_total']?.toString() ?? '0') ?? 0) > max ? (double.tryParse(d['daily_total']?.toString() ?? '0') ?? 0) : max);
return 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('المبيعات اليومية', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
SizedBox(
height: 120,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: daily.map((d) {
final val = double.tryParse(d['daily_total']?.toString() ?? '0') ?? 0;
final height = maxVal > 0 ? (val / maxVal) * 100 : 0.0;
return Expanded(
child: Tooltip(
message: 'يوم ${d['day_num']}: ${val.toStringAsFixed(2)} JOD',
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 1),
height: height,
decoration: BoxDecoration(
color: const Color(0xFF3B82F6).withValues(alpha: 0.7),
borderRadius: const BorderRadius.vertical(top: Radius.circular(3)),
),
),
),
);
}).toList(),
),
),
],
),
);
}
Widget _buildTopSuppliers(List suppliers, bool isDark) {
return 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('أكبر الموردين', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
...suppliers.asMap().entries.map((e) {
final s = e.value;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
CircleAvatar(
radius: 16, backgroundColor: const Color(0xFF0F4C81).withValues(alpha: 0.1),
child: Text('${e.key + 1}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Color(0xFF0F4C81))),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s['supplier_name'] ?? '', style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
Text('${s['invoice_count']} فاتورة', style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)),
],
),
),
Text('${_fmt(s['total_amount'])} JOD', style: const TextStyle(fontWeight: FontWeight.bold, fontFamily: 'monospace', fontSize: 13)),
],
),
);
}),
],
),
);
}
Widget _buildAmountDetails(Map summary, bool isDark) {
return 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('تفاصيل المبالغ', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_amountRow('إجمالي المبيعات (قبل الضريبة)', summary['total_subtotal'], isDark),
const Divider(height: 20),
_amountRow('إجمالي الخصومات', summary['total_discount'], isDark),
const Divider(height: 20),
_amountRow('إجمالي ضريبة المبيعات', summary['total_tax'], isDark, color: const Color(0xFFF59E0B)),
const Divider(height: 20),
_amountRow('صافي المبيعات', summary['total_grand'], isDark, isBold: true),
const Divider(height: 20),
_amountRow('متوسط قيمة الفاتورة', summary['avg_invoice_amount'], isDark),
const Divider(height: 20),
_amountRow('أعلى فاتورة', summary['max_invoice_amount'], isDark),
],
),
);
}
Widget _amountRow(String label, dynamic value, bool isDark, {bool isBold = false, Color? color}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 13, color: color ?? (isDark ? Colors.white70 : Colors.grey.shade600))),
Text(
'${_fmt(value)} JOD',
style: TextStyle(
fontSize: isBold ? 17 : 14,
fontWeight: isBold ? FontWeight.w900 : FontWeight.w600,
color: color ?? (isDark ? Colors.white : Colors.black87),
fontFamily: 'monospace',
),
),
],
);
}
String _fmt(dynamic v) {
final n = double.tryParse(v?.toString() ?? '0') ?? 0;
return n.toStringAsFixed(2);
}
}