import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/invoice_detail_controller.dart'; class InvoiceDetailView extends StatelessWidget { const InvoiceDetailView({super.key}); @override Widget build(BuildContext context) { final controller = Get.put(InvoiceDetailController()); 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, actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () => controller.fetchInvoiceDetails(), ), ], ), body: Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator(color: Color(0xFF0F4C81))); } if (controller.invoice.isEmpty) { return const Center(child: Text('لم يتم العثور على الفاتورة')); } final inv = controller.invoice; final status = inv['status'] ?? 'pending'; final items = (inv['items'] as List?) ?? []; final warnings = (inv['validation_warnings'] as List?) ?? []; final jofotara = inv['jofotara']; return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ─── Header Card ─── _buildHeaderCard(inv, status, isDark), const SizedBox(height: 16), // ─── AI Warnings ─── if (warnings.isNotEmpty) ...[ _buildWarningsCard(warnings, isDark), const SizedBox(height: 16), ], // ─── JoFotara Status ─── if (jofotara != null) ...[ _buildJoFotaraCard(jofotara, isDark), const SizedBox(height: 16), ], // ─── Supplier & Buyer ─── _buildPartiesCard(inv, isDark), const SizedBox(height: 16), // ─── Invoice Lines ─── if (items.isNotEmpty) ...[ _buildLinesCard(items, isDark), const SizedBox(height: 16), ], // ─── Amounts Card ─── _buildAmountsCard(inv, isDark), const SizedBox(height: 32), // ─── 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( onPressed: () => controller.approveInvoice(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF10B981), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.check_circle_outline), label: const Text('اعتماد الفاتورة نهائياً', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ), const SizedBox(height: 12), ], SizedBox( height: 52, child: ElevatedButton.icon( onPressed: () => controller.printThermalInvoice(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4F46E5), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.print_rounded), label: const Text('طباعة حرارية (WiFi)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ), const SizedBox(height: 12), SizedBox( height: 52, child: OutlinedButton.icon( onPressed: () => controller.viewOriginalImage(), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF0F4C81), side: const BorderSide(color: Color(0xFF0F4C81)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.image_outlined), label: const Text('عرض صورة الفاتورة الأصلية', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ), if (jofotara == null && status == 'approved') ...[ const SizedBox(height: 12), SizedBox( height: 52, child: ElevatedButton.icon( onPressed: () => controller.submitToJoFotara(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6366F1), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.send_rounded), label: const Text('إرسال لجوفوترا', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ), ], const SizedBox(height: 40), ], ), ); }), ); } // ───────────────────────────────────────────── // Header Card // ───────────────────────────────────────────── Widget _buildHeaderCard(Map inv, String status, 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( children: [ Text( inv['supplier_name'] ?? inv['company_name'] ?? 'بدون اسم', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: isDark ? Colors.white : const Color(0xFF0F172A), ), textAlign: TextAlign.center, ), const SizedBox(height: 4), Text( 'فاتورة ضريبية ${inv['invoice_type'] == 'credit' ? '(آجل)' : '(نقدية)'}', style: TextStyle(color: isDark ? Colors.white70 : Colors.grey.shade600, fontSize: 13), ), const SizedBox(height: 16), Text( '${_formatAmount(inv['grand_total'])} JOD', style: TextStyle( fontSize: 32, fontWeight: FontWeight.w900, color: isDark ? const Color(0xFF5EEAD4) : const Color(0xFF0F4C81), fontFamily: 'monospace', ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildStatusChip(status), if (inv['ai_confidence'] != null) ...[ const SizedBox(width: 8), _buildConfidenceChip(inv['ai_confidence'], isDark), ], ], ), ], ), ); } // ───────────────────────────────────────────── // AI Validation Warnings // ───────────────────────────────────────────── Widget _buildWarningsCard(List warnings, bool isDark) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF2D1F00) : const Color(0xFFFFF7ED), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFF59E0B).withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.warning_amber_rounded, color: Color(0xFFF59E0B), size: 20), const SizedBox(width: 8), Text( 'تحذيرات التدقيق الآلي (${warnings.length})', style: const TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFF59E0B)), ), ], ), const SizedBox(height: 12), ...warnings.map((w) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('⚠️ ', style: TextStyle(fontSize: 12)), Expanded( child: Text( w.toString(), style: TextStyle( fontSize: 13, color: isDark ? Colors.white70 : Colors.black87, ), ), ), ], ), )), ], ), ); } // ───────────────────────────────────────────── // JoFotara Status Card // ───────────────────────────────────────────── Widget _buildJoFotaraCard(Map jofotara, bool isDark) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF0A2010) : const Color(0xFFF0FDF4), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.verified, color: Color(0xFF10B981), size: 20), const SizedBox(width: 8), const Text( 'مُقدَّمة لجوفوترا ✓', style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF10B981)), ), ], ), const SizedBox(height: 12), _buildMiniRow('UUID', jofotara['uuid'] ?? '—', isDark), const SizedBox(height: 6), _buildMiniRow('تاريخ التقديم', jofotara['submitted_at'] ?? '—', isDark), if (jofotara['qr_image_uri'] != null) ...[ const SizedBox(height: 12), Center( child: Image.network( jofotara['qr_image_uri'], width: 140, height: 140, errorBuilder: (_, __, ___) => const SizedBox(), ), ), ], ], ), ); } // ───────────────────────────────────────────── // Supplier & Buyer Card // ───────────────────────────────────────────── Widget _buildPartiesCard(Map inv, 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), _buildInfoRow('رقم الفاتورة', inv['invoice_number'] ?? '—', isDark), const Divider(height: 24), _buildInfoRow('تاريخ الإصدار', inv['invoice_date'] ?? '—', isDark), const Divider(height: 24), _buildInfoRow('المورّد', inv['supplier_name'] ?? '—', isDark), const Divider(height: 24), _buildInfoRow('ض.م. المورّد', inv['supplier_tin'] ?? '—', isDark), if ((inv['buyer_name'] ?? '').toString().isNotEmpty) ...[ const Divider(height: 24), _buildInfoRow('العميل', inv['buyer_name'] ?? '—', isDark), ], if ((inv['buyer_tin'] ?? '').toString().isNotEmpty) ...[ const Divider(height: 24), _buildInfoRow('ض.م. العميل', inv['buyer_tin'] ?? '—', isDark), ], ], ), ); } // ───────────────────────────────────────────── // Invoice Lines Card // ───────────────────────────────────────────── Widget _buildLinesCard(List items, 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: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('بنود الفاتورة', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), Text( '${items.length} بنود', style: TextStyle(color: isDark ? Colors.white38 : Colors.grey, fontSize: 13), ), ], ), const SizedBox(height: 16), // Table header Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: BoxDecoration( color: isDark ? Colors.white.withValues(alpha: 0.05) : const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const SizedBox(width: 30, child: Text('#', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12))), const Expanded(child: Text('الوصف', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12))), const SizedBox(width: 40, child: Text('كمية', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), textAlign: TextAlign.center)), const SizedBox(width: 60, child: Text('سعر', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), textAlign: TextAlign.center)), const SizedBox(width: 35, child: Text('ض%', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), textAlign: TextAlign.center)), const SizedBox(width: 65, child: Text('إجمالي', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), textAlign: TextAlign.end)), ], ), ), const SizedBox(height: 4), // Table rows ...items.asMap().entries.map((entry) { final item = entry.value; final isLast = entry.key == items.length - 1; return Container( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), decoration: BoxDecoration( border: isLast ? null : Border(bottom: BorderSide(color: isDark ? Colors.white10 : Colors.grey.shade200)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 30, child: Text( (item['line_number'] ?? entry.key + 1).toString(), style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey), ), ), Expanded( child: Text( item['description'] ?? '', style: const TextStyle(fontSize: 13), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), SizedBox( width: 40, child: Text( _fmtNum(item['quantity']), style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), textAlign: TextAlign.center, ), ), SizedBox( width: 60, child: Text( _fmtNum(item['unit_price']), style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), textAlign: TextAlign.center, ), ), SizedBox( width: 35, child: Text( '${((double.tryParse(item['tax_rate']?.toString() ?? '0') ?? 0) * 100).toStringAsFixed(0)}%', style: TextStyle(fontSize: 11, color: isDark ? Colors.white54 : Colors.grey.shade600), textAlign: TextAlign.center, ), ), SizedBox( width: 65, child: Text( _fmtNum(item['line_total']), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, fontFamily: 'monospace'), textAlign: TextAlign.end, ), ), ], ), ); }), ], ), ); } // ───────────────────────────────────────────── // Amounts Card // ───────────────────────────────────────────── Widget _buildAmountsCard(Map inv, 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), _buildInfoRow('المبلغ الخاضع', '${_formatAmount(inv['subtotal'])} JOD', isDark), if ((double.tryParse(inv['discount_total']?.toString() ?? '0') ?? 0) > 0) ...[ const Divider(height: 24), _buildInfoRow('الخصم', '${_formatAmount(inv['discount_total'])} JOD', isDark), ], const Divider(height: 24), _buildInfoRow('قيمة الضريبة', '${_formatAmount(inv['tax_amount'])} JOD', isDark), const Divider(height: 24), _buildInfoRow('الإجمالي النهائي', '${_formatAmount(inv['grand_total'])} JOD', isDark, isBold: true), ], ), ); } // ───────────────────────────────────────────── // Helper Widgets // ───────────────────────────────────────────── Widget _buildStatusChip(String status) { Color color; String text; switch (status) { case 'approved': color = const Color(0xFF10B981); text = '✓ معتمدة'; break; case 'extracted': color = const Color(0xFF3B82F6); text = 'جاهزة للتدقيق'; break; case 'submitted': color = const Color(0xFF6366F1); text = 'مُقدَّمة لجوفوترا'; break; default: color = const Color(0xFFF59E0B); text = 'قيد المعالجة'; } return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), border: Border.all(color: color.withValues(alpha: 0.2)), ), child: Text(text, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12)), ); } Widget _buildConfidenceChip(dynamic confidence, bool isDark) { final score = (double.tryParse(confidence.toString()) ?? 0) * 100; final color = score >= 90 ? const Color(0xFF10B981) : score >= 70 ? const Color(0xFFF59E0B) : const Color(0xFFEF4444); return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), border: Border.all(color: color.withValues(alpha: 0.2)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.auto_awesome, size: 12, color: color), const SizedBox(width: 4), Text( 'AI ${score.toStringAsFixed(0)}%', style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 11, fontFamily: 'monospace'), ), ], ), ); } Widget _buildInfoRow(String label, String value, bool isDark, {bool isBold = false}) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle(fontSize: 14, color: isDark ? Colors.white70 : Colors.grey.shade600)), Flexible( child: Text( value, style: TextStyle( fontWeight: isBold ? FontWeight.w900 : FontWeight.w600, fontSize: isBold ? 18 : 15, color: isDark ? Colors.white : Colors.black87, fontFamily: value.contains(RegExp(r'[0-9]')) ? 'monospace' : null, ), textAlign: TextAlign.end, ), ), ], ); } Widget _buildMiniRow(String label, String value, bool isDark) { return Row( children: [ Text('$label: ', style: TextStyle(fontSize: 12, color: isDark ? Colors.white54 : Colors.grey)), Expanded( child: Text( value, style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), overflow: TextOverflow.ellipsis, ), ), ], ); } String _formatAmount(dynamic value) { final num = double.tryParse(value?.toString() ?? '0') ?? 0; return num.toStringAsFixed(3); } String _fmtNum(dynamic value) { final num = double.tryParse(value?.toString() ?? '0') ?? 0; 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, ), ), ); } }