import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/finance/add_payment_request_controller.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/validators.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/expense/expense_form_widgets.dart'; import 'package:marco/helpers/widgets/my_confirmation_dialog.dart'; /// Show Payment Request Bottom Sheet Future showPaymentRequestBottomSheet({bool isEdit = false}) { return Get.bottomSheet( _PaymentRequestBottomSheet(isEdit: isEdit), isScrollControlled: true, ); } class _PaymentRequestBottomSheet extends StatefulWidget { final bool isEdit; const _PaymentRequestBottomSheet({this.isEdit = false}); @override State<_PaymentRequestBottomSheet> createState() => _PaymentRequestBottomSheetState(); } class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet> with UIMixin { final PaymentRequestController controller = Get.put(PaymentRequestController()); final _formKey = GlobalKey(); final GlobalKey _projectDropdownKey = GlobalKey(); final GlobalKey _categoryDropdownKey = GlobalKey(); final GlobalKey _payeeDropdownKey = GlobalKey(); final GlobalKey _currencyDropdownKey = GlobalKey(); @override Widget build(BuildContext context) { return Obx( () => Form( key: _formKey, child: BaseBottomSheet( title: widget.isEdit ? "Edit Payment Request" : "Create Payment Request", isSubmitting: false, onCancel: Get.back, onSubmit: () { if (_formKey.currentState!.validate() && _validateSelections()) { // Call your submit API here showAppSnackbar( title: "Success", message: "Payment request submitted!", type: SnackbarType.success, ); Get.back(); } }, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDropdownField( icon: Icons.work_outline, title: " Select Project", requiredField: true, value: controller.selectedProject.value.isEmpty ? "Select Project" : controller.selectedProject.value, onTap: () => _showOptionList( controller.globalProjects.toList(), (p) => p, controller.selectProject, _projectDropdownKey, ), dropdownKey: _projectDropdownKey, ), _gap(), _buildDropdownField( icon: Icons.category_outlined, title: "Expense Category", requiredField: true, value: controller.selectedCategory.value?.name ?? "Select Category", onTap: () => _showOptionList( controller.categories.toList(), (c) => c.name, controller.selectCategory, _categoryDropdownKey), dropdownKey: _categoryDropdownKey, ), _gap(), _buildTextField( icon: Icons.title_outlined, title: "Title", controller: TextEditingController(), hint: "Enter title", validator: Validators.requiredField, ), _gap(), // Is Advance Payment Radio Buttons with Icon and Primary Color Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.attach_money_outlined, size: 20), SizedBox(width: 6), Text( "Is Advance Payment", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), ], ), MySpacing.height(6), Obx(() => Row( children: [ Expanded( child: RadioListTile( contentPadding: EdgeInsets.zero, title: Text("Yes"), value: true, groupValue: controller.isAdvancePayment.value, activeColor: contentTheme.primary, onChanged: (val) { if (val != null) controller.isAdvancePayment.value = val; }, ), ), Expanded( child: RadioListTile( contentPadding: EdgeInsets.zero, title: Text("No"), value: false, groupValue: controller.isAdvancePayment.value, activeColor: contentTheme.primary, onChanged: (val) { if (val != null) controller.isAdvancePayment.value = val; }, ), ), ], )), _gap(), ], ), _buildTextField( icon: Icons.calendar_today, title: "Due To Date", controller: TextEditingController(), hint: "DD-MM-YYYY", validator: Validators.requiredField, ), _gap(), _buildTextField( icon: Icons.currency_rupee, title: "Amount", controller: TextEditingController(), hint: "Enter Amount", keyboardType: TextInputType.number, validator: (v) => (v != null && v.isNotEmpty && double.tryParse(v) != null) ? null : "Enter valid amount", ), _gap(), _buildDropdownField( icon: Icons.person_outline, title: "Payee", requiredField: true, value: controller.selectedPayee.value.isEmpty ? "Select Payee" : controller.selectedPayee.value, onTap: () => _showOptionList(controller.payees.toList(), (p) => p, controller.selectPayee, _payeeDropdownKey), dropdownKey: _payeeDropdownKey, ), _gap(), _buildDropdownField( icon: Icons.monetization_on_outlined, title: "Currency", requiredField: true, value: controller.selectedCurrency.value?.currencyName ?? "Select Currency", onTap: () => _showOptionList( controller.currencies.toList(), (c) => c.currencyName, // <-- changed here controller.selectCurrency, _currencyDropdownKey), dropdownKey: _currencyDropdownKey, ), _gap(), _buildTextField( icon: Icons.description_outlined, title: "Description", controller: TextEditingController(), hint: "Enter description", maxLines: 3, validator: Validators.requiredField, ), _gap(), _buildAttachmentsSection(), ], ), ), ), ), ); } Widget _gap([double h = 16]) => MySpacing.height(h); Widget _buildDropdownField({ required IconData icon, required String title, required bool requiredField, required String value, required VoidCallback onTap, required GlobalKey dropdownKey, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SectionTitle(icon: icon, title: title, requiredField: requiredField), MySpacing.height(6), DropdownTile(key: dropdownKey, title: value, onTap: onTap), ], ); } Widget _buildTextField({ required IconData icon, required String title, required TextEditingController controller, String? hint, TextInputType? keyboardType, FormFieldValidator? validator, int maxLines = 1, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SectionTitle( icon: icon, title: title, requiredField: validator != null), MySpacing.height(6), CustomTextField( controller: controller, hint: hint ?? "", keyboardType: keyboardType ?? TextInputType.text, validator: validator, maxLines: maxLines, ), ], ); } Widget _buildAttachmentsSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SectionTitle( icon: Icons.attach_file, title: "Attachments", requiredField: true, ), MySpacing.height(10), Obx(() { if (controller.isProcessingAttachment.value) { return Center( child: Column( children: [ CircularProgressIndicator( color: contentTheme.primary, ), const SizedBox(height: 8), Text( "Processing image, please wait...", style: TextStyle( fontSize: 14, color: contentTheme.primary, ), ), ], ), ); } return AttachmentsSection( attachments: controller.attachments, existingAttachments: controller.existingAttachments, onRemoveNew: controller.removeAttachment, controller: controller, onRemoveExisting: (item) async { await showDialog( context: context, barrierDismissible: false, builder: (_) => ConfirmDialog( title: "Remove Attachment", message: "Are you sure you want to remove this attachment?", confirmText: "Remove", icon: Icons.delete, confirmColor: Colors.redAccent, onConfirm: () async { final index = controller.existingAttachments.indexOf(item); if (index != -1) { controller.existingAttachments[index]['isActive'] = false; controller.existingAttachments.refresh(); } showAppSnackbar( title: 'Removed', message: 'Attachment has been removed.', type: SnackbarType.success, ); Navigator.pop(context); }, ), ); }, onAdd: controller.pickAttachments, ); }), ], ); } /// Generic option list for dropdowns Future _showOptionList(List options, String Function(T) getLabel, ValueChanged onSelected, GlobalKey key) async { if (options.isEmpty) { _showError("No options available"); return; } final RenderBox button = key.currentContext!.findRenderObject() as RenderBox; final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final position = button.localToGlobal(Offset.zero, ancestor: overlay); final selected = await showMenu( context: context, position: RelativeRect.fromLTRB( position.dx, position.dy + button.size.height, overlay.size.width - position.dx - button.size.width, 0, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), items: options .map((opt) => PopupMenuItem( value: opt, child: Text(getLabel(opt)), )) .toList(), ); if (selected != null) onSelected(selected); } bool _validateSelections() { if (controller.selectedProject.value.isEmpty) { _showError("Please select a project"); return false; } if (controller.selectedCategory.value == null) { _showError("Please select a category"); return false; } if (controller.selectedPayee.value.isEmpty) { _showError("Please select a payee"); return false; } if (controller.selectedCurrency.value == null) { _showError("Please select currency"); return false; } return true; } void _showError(String msg) { showAppSnackbar( title: "Error", message: msg, type: SnackbarType.error, ); } }