import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:marco/controller/expense/expense_detail_controller.dart'; import 'package:marco/helpers/utils/date_time_utils.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/model/expense/expense_list_model.dart'; import 'package:marco/controller/project_controller.dart'; class ExpenseDetailScreen extends StatelessWidget { final String expenseId; const ExpenseDetailScreen({super.key, required this.expenseId}); // Status color logic static Color getStatusColor(String? status, {String? colorCode}) { if (colorCode != null && colorCode.isNotEmpty) { try { return Color(int.parse(colorCode.replaceFirst('#', '0xff'))); } catch (_) {} } switch (status) { case 'Approval Pending': return Colors.orange; case 'Process Pending': return Colors.blue; case 'Rejected': return Colors.red; case 'Paid': return Colors.green; default: return Colors.black; } } @override Widget build(BuildContext context) { final controller = Get.put(ExpenseDetailController()); final projectController = Get.find(); controller.fetchExpenseDetails(expenseId); return Scaffold( backgroundColor: const Color(0xFFF7F7F7), appBar: PreferredSize( preferredSize: const Size.fromHeight(72), child: AppBar( backgroundColor: Colors.white, elevation: 1, automaticallyImplyLeading: false, titleSpacing: 0, title: Padding( padding: MySpacing.xy(16, 0), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offAllNamed('/dashboard/expense-main-page'), ), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ MyText.titleLarge( 'Expense Details', fontWeight: 700, color: Colors.black, ), MySpacing.height(2), Obx(() { final projectName = projectController.selectedProject?.name ?? 'Select Project'; return InkWell( onTap: () => Get.toNamed('/project-selector'), child: Row( children: [ const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( projectName, fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.grey[700], ), ), ], ), ); }), ], ), ), ], ), ), ), ), body: SafeArea( child: Obx(() { if (controller.isLoading.value) { return _buildLoadingSkeleton(); } if (controller.errorMessage.isNotEmpty) { return Center( child: Text( controller.errorMessage.value, style: const TextStyle(color: Colors.red, fontSize: 16), ), ); } final expense = controller.expense.value; if (expense == null) { return const Center(child: Text("No expense details found.")); } final statusColor = getStatusColor( expense.status.name, colorCode: expense.status.color, ); final formattedAmount = NumberFormat.currency( locale: 'en_IN', symbol: '₹ ', decimalDigits: 2, ).format(expense.amount); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _ExpenseHeader( title: expense.expensesType.name, amount: formattedAmount, status: expense.status.name, statusColor: statusColor, ), const SizedBox(height: 16), _ExpenseDetailsList(expense: expense), const SizedBox(height: 100), ], ), ); }), ), bottomNavigationBar: Obx(() { final expense = controller.expense.value; if (expense == null || expense.nextStatus.isEmpty) { return const SizedBox(); } return SafeArea( child: Container( color: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: Wrap( alignment: WrapAlignment.center, spacing: 10, runSpacing: 10, children: expense.nextStatus.map((next) { Color buttonColor = Colors.red; if (next.color.isNotEmpty) { try { buttonColor = Color(int.parse(next.color.replaceFirst('#', '0xff'))); } catch (_) {} } return ElevatedButton( style: ElevatedButton.styleFrom( minimumSize: const Size(100, 40), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), backgroundColor: buttonColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), onPressed: () async { final success = await controller.updateExpenseStatus( expense.id, next.id); if (success) { Get.snackbar( 'Success', 'Expense moved to ${next.displayName.isNotEmpty ? next.displayName : next.name}', backgroundColor: Colors.green.withOpacity(0.8), colorText: Colors.white, ); await controller.fetchExpenseDetails(expenseId); } else { Get.snackbar( 'Error', 'Failed to update status.', backgroundColor: Colors.red.withOpacity(0.8), colorText: Colors.white, ); } }, child: Text( next.displayName.isNotEmpty ? next.displayName : next.name, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14, ), overflow: TextOverflow.ellipsis, ), ); }).toList(), ), ), ); }), ); } // Loading skeleton placeholder Widget _buildLoadingSkeleton() { return ListView( padding: const EdgeInsets.all(16), children: List.generate(5, (index) { return Container( margin: const EdgeInsets.only(bottom: 16), height: 80, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(10), ), ); }), ); } } // Expense header card class _ExpenseHeader extends StatelessWidget { final String title; final String amount; final String status; final Color statusColor; const _ExpenseHeader({ required this.title, required this.amount, required this.status, required this.statusColor, }); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black, ), ), const SizedBox(height: 6), Text( amount, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.w700, color: Colors.black, ), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: statusColor.withOpacity(0.15), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.flag, size: 16, color: statusColor), const SizedBox(width: 6), Text( status, style: TextStyle( color: statusColor, fontWeight: FontWeight.w600, ), ), ], ), ), ], ), ); } } // Expense details list class _ExpenseDetailsList extends StatelessWidget { final ExpenseModel expense; const _ExpenseDetailsList({required this.expense}); @override Widget build(BuildContext context) { final transactionDate = DateTimeUtils.convertUtcToLocal( expense.transactionDate.toString(), format: 'dd-MM-yyyy hh:mm a', ); final createdAt = DateTimeUtils.convertUtcToLocal( expense.createdAt.toString(), format: 'dd-MM-yyyy hh:mm a', ); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _DetailRow(title: "Project", value: expense.project.name), _DetailRow(title: "Expense Type", value: expense.expensesType.name), _DetailRow(title: "Payment Mode", value: expense.paymentMode.name), _DetailRow( title: "Paid By", value: '${expense.paidBy.firstName} ${expense.paidBy.lastName}', ), _DetailRow( title: "Created By", value: '${expense.createdBy.firstName} ${expense.createdBy.lastName}', ), _DetailRow(title: "Transaction Date", value: transactionDate), _DetailRow(title: "Created At", value: createdAt), _DetailRow(title: "Supplier Name", value: expense.supplerName), _DetailRow( title: "Amount", value: NumberFormat.currency( locale: 'en_IN', symbol: '₹ ', decimalDigits: 2, ).format(expense.amount), ), _DetailRow(title: "Status", value: expense.status.name), _DetailRow( title: "Next Status", value: expense.nextStatus.map((e) => e.name).join(", "), ), _DetailRow( title: "Pre-Approved", value: expense.preApproved ? "Yes" : "No", ), ], ), ); } } // A single row for expense details class _DetailRow extends StatelessWidget { final String title; final String value; const _DetailRow({required this.title, required this.value}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 3, child: Text( title, style: const TextStyle( fontSize: 13, color: Colors.grey, fontWeight: FontWeight.w500, ), ), ), Expanded( flex: 5, child: Text( value, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), softWrap: true, ), ), ], ), ); } }