diff --git a/lib/controller/expense/add_expense_controller.dart b/lib/controller/expense/add_expense_controller.dart index abf0389..a3c6b8a 100644 --- a/lib/controller/expense/add_expense_controller.dart +++ b/lib/controller/expense/add_expense_controller.dart @@ -18,7 +18,6 @@ import 'package:marco/model/expense/expense_type_model.dart'; import 'package:marco/model/expense/payment_types_model.dart'; class AddExpenseController extends GetxController { - // === Text Controllers === final amountController = TextEditingController(); final descriptionController = TextEditingController(); final supplierController = TextEditingController(); @@ -28,21 +27,17 @@ class AddExpenseController extends GetxController { final transactionDateController = TextEditingController(); final TextEditingController noOfPersonsController = TextEditingController(); - // === State Controllers === final RxBool isLoading = false.obs; final RxBool isSubmitting = false.obs; final RxBool isFetchingLocation = false.obs; - // === Selected Models === final Rx selectedPaymentMode = Rx(null); final Rx selectedExpenseType = Rx(null); - final Rx selectedExpenseStatus = - Rx(null); + final Rx selectedExpenseStatus = Rx(null); final Rx selectedPaidBy = Rx(null); final RxString selectedProject = ''.obs; final Rx selectedTransactionDate = Rx(null); - // === Lists === final RxList attachments = [].obs; final RxList globalProjects = [].obs; final RxList projects = [].obs; @@ -51,7 +46,6 @@ class AddExpenseController extends GetxController { final RxList expenseStatuses = [].obs; final RxList allEmployees = [].obs; - // === Mappings === final RxMap projectsMap = {}.obs; final ExpenseController expenseController = Get.find(); @@ -77,7 +71,6 @@ class AddExpenseController extends GetxController { super.onClose(); } - // === Pick Attachments === Future pickAttachments() async { try { final result = await FilePicker.platform.pickFiles( @@ -86,12 +79,15 @@ class AddExpenseController extends GetxController { allowMultiple: true, ); if (result != null && result.paths.isNotEmpty) { - final files = - result.paths.whereType().map((e) => File(e)).toList(); + final files = result.paths.whereType().map((e) => File(e)).toList(); attachments.addAll(files); } } catch (e) { - Get.snackbar("Error", "Failed to pick attachments: $e"); + showAppSnackbar( + title: "Error", + message: "Failed to pick attachments: $e", + type: SnackbarType.error, + ); } } @@ -99,16 +95,14 @@ class AddExpenseController extends GetxController { attachments.remove(file); } - // === Date Picker === void pickTransactionDate(BuildContext context) async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: selectedTransactionDate.value ?? now, firstDate: DateTime(now.year - 5), - lastDate: now, // ✅ Restrict future dates + lastDate: now, ); - if (picked != null) { selectedTransactionDate.value = picked; transactionDateController.text = @@ -116,31 +110,33 @@ class AddExpenseController extends GetxController { } } - // === Fetch Current Location === Future fetchCurrentLocation() async { isFetchingLocation.value = true; try { LocationPermission permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied || - permission == LocationPermission.deniedForever) { + if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) { permission = await Geolocator.requestPermission(); - if (permission == LocationPermission.denied || - permission == LocationPermission.deniedForever) { - Get.snackbar( - "Error", "Location permission denied. Enable in settings."); + if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) { + showAppSnackbar( + title: "Error", + message: "Location permission denied. Enable in settings.", + type: SnackbarType.error, + ); return; } } if (!await Geolocator.isLocationServiceEnabled()) { - Get.snackbar("Error", "Location services are disabled. Enable them."); + showAppSnackbar( + title: "Error", + message: "Location services are disabled. Enable them.", + type: SnackbarType.error, + ); return; } - final position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high); - final placemarks = - await placemarkFromCoordinates(position.latitude, position.longitude); + final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); + final placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); if (placemarks.isNotEmpty) { final place = placemarks.first; @@ -157,13 +153,16 @@ class AddExpenseController extends GetxController { locationController.text = "${position.latitude}, ${position.longitude}"; } } catch (e) { - Get.snackbar("Error", "Error fetching location: $e"); + showAppSnackbar( + title: "Error", + message: "Error fetching location: $e", + type: SnackbarType.error, + ); } finally { isFetchingLocation.value = false; } } - // === Submit Expense === Future submitExpense() async { if (isSubmitting.value) return; isSubmitting.value = true; @@ -239,8 +238,7 @@ class AddExpenseController extends GetxController { expensesTypeId: selectedExpenseType.value!.id, paymentModeId: selectedPaymentMode.value!.id, paidById: selectedPaidBy.value!.id, - transactionDate: - (selectedTransactionDate.value ?? DateTime.now()).toUtc(), + transactionDate: selectedTransactionDate.value?.toUtc() ?? DateTime.now().toUtc(), transactionId: transactionIdController.text, description: descriptionController.text, location: locationController.text, @@ -278,7 +276,6 @@ class AddExpenseController extends GetxController { } } - // === Fetch Data Methods === Future fetchMasterData() async { try { final expenseTypesData = await ApiService.getMasterExpenseTypes(); @@ -286,22 +283,22 @@ class AddExpenseController extends GetxController { final expenseStatusData = await ApiService.getMasterExpenseStatus(); if (expenseTypesData is List) { - expenseTypes.value = - expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList(); + expenseTypes.value = expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList(); } if (paymentModesData is List) { - paymentModes.value = - paymentModesData.map((e) => PaymentModeModel.fromJson(e)).toList(); + paymentModes.value = paymentModesData.map((e) => PaymentModeModel.fromJson(e)).toList(); } if (expenseStatusData is List) { - expenseStatuses.value = expenseStatusData - .map((e) => ExpenseStatusModel.fromJson(e)) - .toList(); + expenseStatuses.value = expenseStatusData.map((e) => ExpenseStatusModel.fromJson(e)).toList(); } } catch (e) { - Get.snackbar("Error", "Failed to fetch master data: $e"); + showAppSnackbar( + title: "Error", + message: "Failed to fetch master data: $e", + type: SnackbarType.error, + ); } } @@ -332,8 +329,7 @@ class AddExpenseController extends GetxController { final response = await ApiService.getAllEmployees(); if (response != null && response.isNotEmpty) { allEmployees.assignAll(response.map((e) => EmployeeModel.fromJson(e))); - logSafe("All Employees fetched: ${allEmployees.length}", - level: LogLevel.info); + logSafe("All Employees fetched: ${allEmployees.length}", level: LogLevel.info); } else { allEmployees.clear(); logSafe("No employees found.", level: LogLevel.warning); diff --git a/lib/controller/expense/expense_screen_controller.dart b/lib/controller/expense/expense_screen_controller.dart index 078b4ad..d04c3ac 100644 --- a/lib/controller/expense/expense_screen_controller.dart +++ b/lib/controller/expense/expense_screen_controller.dart @@ -7,6 +7,8 @@ import 'package:marco/model/expense/payment_types_model.dart'; import 'package:marco/model/expense/expense_type_model.dart'; import 'package:marco/model/expense/expense_status_model.dart'; import 'package:marco/model/employee_model.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; + class ExpenseController extends GetxController { final RxList expenses = [].obs; @@ -68,15 +70,27 @@ class ExpenseController extends GetxController { if (success) { expenses.removeWhere((e) => e.id == expenseId); logSafe("Expense deleted successfully."); - Get.snackbar("Deleted", "Expense has been deleted successfully."); + showAppSnackbar( + title: "Deleted", + message: "Expense has been deleted successfully.", + type: SnackbarType.success, + ); } else { logSafe("Failed to delete expense: $expenseId", level: LogLevel.error); - Get.snackbar("Failed", "Failed to delete expense."); + showAppSnackbar( + title: "Failed", + message: "Failed to delete expense.", + type: SnackbarType.error, + ); } } catch (e, stack) { logSafe("Exception in deleteExpense: $e", level: LogLevel.error); logSafe("StackTrace: $stack", level: LogLevel.debug); - Get.snackbar("Error", "Something went wrong while deleting."); + showAppSnackbar( + title: "Error", + message: "Something went wrong while deleting.", + type: SnackbarType.error, + ); } } @@ -159,29 +173,32 @@ class ExpenseController extends GetxController { /// Fetch master data: expense types, payment modes, and expense status Future fetchMasterData() async { - try { - final expenseTypesData = await ApiService.getMasterExpenseTypes(); - if (expenseTypesData is List) { - expenseTypes.value = - expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList(); - } - - final paymentModesData = await ApiService.getMasterPaymentModes(); - if (paymentModesData is List) { - paymentModes.value = - paymentModesData.map((e) => PaymentModeModel.fromJson(e)).toList(); - } - - final expenseStatusData = await ApiService.getMasterExpenseStatus(); - if (expenseStatusData is List) { - expenseStatuses.value = expenseStatusData - .map((e) => ExpenseStatusModel.fromJson(e)) - .toList(); - } - } catch (e) { - Get.snackbar("Error", "Failed to fetch master data: $e"); + try { + final expenseTypesData = await ApiService.getMasterExpenseTypes(); + if (expenseTypesData is List) { + expenseTypes.value = + expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList(); } + + final paymentModesData = await ApiService.getMasterPaymentModes(); + if (paymentModesData is List) { + paymentModes.value = + paymentModesData.map((e) => PaymentModeModel.fromJson(e)).toList(); + } + + final expenseStatusData = await ApiService.getMasterExpenseStatus(); + if (expenseStatusData is List) { + expenseStatuses.value = + expenseStatusData.map((e) => ExpenseStatusModel.fromJson(e)).toList(); + } + } catch (e) { + showAppSnackbar( + title: "Error", + message: "Failed to fetch master data: $e", + type: SnackbarType.error, + ); } +} /// Fetch global projects Future fetchGlobalProjects() async { diff --git a/lib/model/expense/reimbursement_bottom_sheet.dart b/lib/model/expense/reimbursement_bottom_sheet.dart index b22b26d..5c51956 100644 --- a/lib/model/expense/reimbursement_bottom_sheet.dart +++ b/lib/model/expense/reimbursement_bottom_sheet.dart @@ -7,6 +7,8 @@ import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; + class ReimbursementBottomSheet extends StatefulWidget { final String expenseId; @@ -34,7 +36,8 @@ class ReimbursementBottomSheet extends StatefulWidget { } class _ReimbursementBottomSheetState extends State { - final ExpenseDetailController controller = Get.find(); + final ExpenseDetailController controller = + Get.find(); final TextEditingController commentCtrl = TextEditingController(); final TextEditingController txnCtrl = TextEditingController(); @@ -119,7 +122,11 @@ class _ReimbursementBottomSheetState extends State { txnCtrl.text.trim().isEmpty || dateStr.value.isEmpty || controller.selectedReimbursedBy.value == null) { - Get.snackbar("Incomplete", "Please fill all fields"); + showAppSnackbar( + title: "Incomplete", + message: "Please fill all fields", + type: SnackbarType.warning, + ); return; } @@ -133,9 +140,17 @@ class _ReimbursementBottomSheetState extends State { if (success) { Get.back(); - Get.snackbar('Success', 'Reimbursement submitted'); + showAppSnackbar( + title: "Success", + message: "Reimbursement submitted", + type: SnackbarType.success, + ); } else { - Get.snackbar('Error', controller.errorMessage.value); + showAppSnackbar( + title: "Error", + message: controller.errorMessage.value, + type: SnackbarType.error, + ); } }, child: Column( @@ -148,7 +163,6 @@ class _ReimbursementBottomSheetState extends State { decoration: _inputDecoration("Enter comment"), ), MySpacing.height(16), - MyText.labelMedium("Transaction ID"), MySpacing.height(8), TextField( @@ -156,7 +170,6 @@ class _ReimbursementBottomSheetState extends State { decoration: _inputDecoration("Enter transaction ID"), ), MySpacing.height(16), - MyText.labelMedium("Reimbursement Date"), MySpacing.height(8), GestureDetector( @@ -183,7 +196,6 @@ class _ReimbursementBottomSheetState extends State { ), ), MySpacing.height(16), - MyText.labelMedium("Reimbursed By"), MySpacing.height(8), GestureDetector( diff --git a/lib/view/expense/expense_detail_screen.dart b/lib/view/expense/expense_detail_screen.dart index 6718ecd..9af777e 100644 --- a/lib/view/expense/expense_detail_screen.dart +++ b/lib/view/expense/expense_detail_screen.dart @@ -12,6 +12,7 @@ import 'package:marco/model/expense/expense_detail_model.dart'; import 'package:marco/helpers/widgets/image_viewer_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:marco/model/expense/reimbursement_bottom_sheet.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; class ExpenseDetailScreen extends StatelessWidget { final String expenseId; @@ -224,21 +225,18 @@ class ExpenseDetailScreen extends StatelessWidget { ); if (success) { - Get.snackbar( - 'Success', - 'Expense reimbursed successfully.', - backgroundColor: Colors.green.withOpacity(0.8), - colorText: Colors.white, + showAppSnackbar( + title: 'Success', + message: 'Expense reimbursed successfully.', + type: SnackbarType.success, ); await controller.fetchExpenseDetails(); - return true; } else { - Get.snackbar( - 'Error', - 'Failed to reimburse expense.', - backgroundColor: Colors.red.withOpacity(0.8), - colorText: Colors.white, + showAppSnackbar( + title: 'Error', + message: 'Failed to reimburse expense.', + type: SnackbarType.error, ); return false; } @@ -250,19 +248,18 @@ class ExpenseDetailScreen extends StatelessWidget { await controller.updateExpenseStatus(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, + showAppSnackbar( + title: 'Success', + message: + 'Expense moved to ${next.displayName.isNotEmpty ? next.displayName : next.name}', + type: SnackbarType.success, ); await controller.fetchExpenseDetails(); } else { - Get.snackbar( - 'Error', - 'Failed to update status.', - backgroundColor: Colors.red.withOpacity(0.8), - colorText: Colors.white, + showAppSnackbar( + title: 'Error', + message: 'Failed to update status.', + type: SnackbarType.error, ); } } @@ -491,7 +488,11 @@ class _InvoiceDocuments extends StatelessWidget { if (await canLaunchUrl(url)) { await launchUrl(url, mode: LaunchMode.externalApplication); } else { - Get.snackbar("Error", "Could not open the document."); + showAppSnackbar( + title: 'Error', + message: 'Could not open the document.', + type: SnackbarType.error, + ); } } },