From adf5e1437ec699dcaa3b33dee1846817f2ce5f90 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Thu, 31 Jul 2025 11:09:25 +0530 Subject: [PATCH] feat: make statusId dynamic in reimbursement handling and update related components --- .../expense/expense_detail_controller.dart | 3 +- .../expense/reimbursement_bottom_sheet.dart | 56 ++++++++++++------- lib/view/expense/expense_detail_screen.dart | 37 ++++++++---- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/lib/controller/expense/expense_detail_controller.dart b/lib/controller/expense/expense_detail_controller.dart index 6d246a6..ab47eaf 100644 --- a/lib/controller/expense/expense_detail_controller.dart +++ b/lib/controller/expense/expense_detail_controller.dart @@ -81,6 +81,7 @@ class ExpenseDetailController extends GetxController { required String reimburseTransactionId, required String reimburseDate, required String reimburseById, + required String statusId, // ✅ dynamic }) async { isLoading.value = true; errorMessage.value = ''; @@ -90,7 +91,7 @@ class ExpenseDetailController extends GetxController { final success = await ApiService.updateExpenseStatusApi( expenseId: expenseId, - statusId: 'reimbursed', + statusId: statusId, // ✅ now dynamic comment: comment, reimburseTransactionId: reimburseTransactionId, reimburseDate: reimburseDate, diff --git a/lib/model/expense/reimbursement_bottom_sheet.dart b/lib/model/expense/reimbursement_bottom_sheet.dart index 3c15655..016da67 100644 --- a/lib/model/expense/reimbursement_bottom_sheet.dart +++ b/lib/model/expense/reimbursement_bottom_sheet.dart @@ -6,14 +6,14 @@ import 'package:marco/helpers/widgets/my_text.dart'; class ReimbursementBottomSheet extends StatefulWidget { final String expenseId; - final String statusId; + final String statusId; final void Function() onClose; final Future Function({ required String comment, required String reimburseTransactionId, required String reimburseDate, required String reimburseById, - + required String statusId, }) onSubmit; const ReimbursementBottomSheet({ @@ -21,16 +21,17 @@ class ReimbursementBottomSheet extends StatefulWidget { required this.expenseId, required this.onClose, required this.onSubmit, - required this.statusId, - + required this.statusId, }); @override - State createState() => _ReimbursementBottomSheetState(); + State createState() => + _ReimbursementBottomSheetState(); } class _ReimbursementBottomSheetState extends State { - final ExpenseDetailController controller = Get.find(); + final ExpenseDetailController controller = + Get.find(); final TextEditingController commentCtrl = TextEditingController(); final TextEditingController txnCtrl = TextEditingController(); @@ -47,13 +48,15 @@ class _ReimbursementBottomSheetState extends State { showModalBottomSheet( context: context, backgroundColor: Colors.white, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (_) { return SizedBox( height: 300, child: Obx(() { final employees = controller.allEmployees; - if (employees.isEmpty) return const Center(child: Text("No employees found")); + if (employees.isEmpty) + return const Center(child: Text("No employees found")); return ListView.builder( itemCount: employees.length, itemBuilder: (_, index) { @@ -113,7 +116,8 @@ class _ReimbursementBottomSheetState extends State { children: [ _buildInputField(label: 'Comment', controller: commentCtrl), const SizedBox(height: 16), - _buildInputField(label: 'Transaction ID', controller: txnCtrl), + _buildInputField( + label: 'Transaction ID', controller: txnCtrl), const SizedBox(height: 16), _buildDatePickerField(), const SizedBox(height: 16), @@ -130,12 +134,14 @@ class _ReimbursementBottomSheetState extends State { ); } - Widget _buildInputField({required String label, required TextEditingController controller}) { + Widget _buildInputField( + {required String label, required TextEditingController controller}) { return TextField( controller: controller, decoration: InputDecoration( labelText: label, - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), @@ -163,7 +169,8 @@ class _ReimbursementBottomSheetState extends State { child: InputDecorator( decoration: InputDecoration( labelText: 'Reimbursement Date', - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), @@ -194,7 +201,8 @@ class _ReimbursementBottomSheetState extends State { child: InputDecorator( decoration: InputDecoration( labelText: 'Reimbursed By', - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), @@ -206,7 +214,9 @@ class _ReimbursementBottomSheetState extends State { : '${controller.selectedReimbursedBy.value?.firstName ?? ''} ${controller.selectedReimbursedBy.value?.lastName ?? ''}', style: TextStyle( fontSize: 14, - color: controller.selectedReimbursedBy.value == null ? Colors.grey : Colors.black, + color: controller.selectedReimbursedBy.value == null + ? Colors.grey + : Colors.black, ), ), ), @@ -224,11 +234,13 @@ class _ReimbursementBottomSheetState extends State { Get.back(); }, icon: const Icon(Icons.close, color: Colors.white), - label: MyText.bodyMedium("Cancel", color: Colors.white, fontWeight: 600), + label: MyText.bodyMedium("Cancel", + color: Colors.white, fontWeight: 600), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 7), ), ), ), @@ -251,7 +263,9 @@ class _ReimbursementBottomSheetState extends State { comment: commentCtrl.text.trim(), reimburseTransactionId: txnCtrl.text.trim(), reimburseDate: dateStr.value, - reimburseById: controller.selectedReimbursedBy.value!.id, + reimburseById: + controller.selectedReimbursedBy.value!.id, + statusId: widget.statusId, ); if (success) { @@ -269,8 +283,10 @@ class _ReimbursementBottomSheetState extends State { ), style: ElevatedButton.styleFrom( backgroundColor: Colors.indigo, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: + const EdgeInsets.symmetric(horizontal: 5, vertical: 7), ), ); }), diff --git a/lib/view/expense/expense_detail_screen.dart b/lib/view/expense/expense_detail_screen.dart index 698f965..2877090 100644 --- a/lib/view/expense/expense_detail_screen.dart +++ b/lib/view/expense/expense_detail_screen.dart @@ -90,22 +90,28 @@ class ExpenseDetailScreen extends StatelessWidget { ), body: SafeArea( child: Obx(() { + // Show error snackbar only once after frame render + if (controller.errorMessage.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Get.snackbar( + "Error", + controller.errorMessage.value, + backgroundColor: Colors.red.withOpacity(0.9), + colorText: Colors.white, + ); + controller.errorMessage.value = ''; + }); + } + if (controller.isLoading.value) { return _buildLoadingSkeleton(); } - if (controller.errorMessage.isNotEmpty) { - return Center( - child: MyText.bodyMedium( - controller.errorMessage.value, - color: Colors.red, - ), - ); - } final expense = controller.expense.value; if (expense == null) { return Center( - child: MyText.bodyMedium("No expense details found.")); + child: MyText.bodyMedium("No expense details found."), + ); } final statusColor = getStatusColor(expense.status.name, @@ -116,8 +122,14 @@ class ExpenseDetailScreen extends StatelessWidget { decimalDigits: 2, ).format(expense.amount); + // === CHANGE: Add proper bottom padding to always keep content away from device nav bar === return SingleChildScrollView( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.fromLTRB( + 8, + 8, + 8, + 16 + MediaQuery.of(context).padding.bottom, // KEY LINE + ), child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 520), @@ -199,13 +211,13 @@ class ExpenseDetailScreen extends StatelessWidget { builder: (context) => ReimbursementBottomSheet( expenseId: expense.id, statusId: next.id, - onClose: - () {}, // <-- This is the missing required parameter + onClose: () {}, onSubmit: ({ required String comment, required String reimburseTransactionId, required String reimburseDate, required String reimburseById, + required String statusId, }) async { final success = await controller .updateExpenseStatusWithReimbursement( @@ -214,6 +226,7 @@ class ExpenseDetailScreen extends StatelessWidget { reimburseTransactionId: reimburseTransactionId, reimburseDate: reimburseDate, reimburseById: reimburseById, + statusId: statusId, ); if (success) {