import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:on_field_work/controller/finance/advance_payment_controller.dart'; import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; class AdvancePaymentScreen extends StatefulWidget { const AdvancePaymentScreen({super.key}); @override State createState() => _AdvancePaymentScreenState(); } class _AdvancePaymentScreenState extends State with UIMixin { late final AdvancePaymentController controller; late final TextEditingController _searchCtrl; final FocusNode _searchFocus = FocusNode(); final projectController = Get.find(); @override void initState() { super.initState(); controller = Get.put(AdvancePaymentController()); _searchCtrl = TextEditingController(); WidgetsBinding.instance.addPostFrameCallback((_) { final employeeId = Get.arguments?['employeeId'] ?? ''; if (employeeId.isNotEmpty) { controller.fetchAdvancePayments(employeeId); } }); _searchCtrl.addListener(() { controller.searchQuery.value = _searchCtrl.text.trim(); }); } @override void dispose() { _searchCtrl.dispose(); _searchFocus.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), backgroundColor: const Color(0xFFF5F5F5), appBar: _buildAppBar(), body: SafeArea( child: LayoutBuilder( builder: (context, constraints) { final bool isLandscape = constraints.maxWidth > constraints.maxHeight; return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: RefreshIndicator( onRefresh: () async { final emp = controller.selectedEmployee.value; if (emp != null) { await controller.fetchAdvancePayments(emp.id.toString()); } }, color: Colors.white, backgroundColor: contentTheme.primary, strokeWidth: 2.5, displacement: 60, // ---------------- PORTRAIT (UNCHANGED) ---------------- child: !isLandscape ? SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Container( color: const Color(0xFFF5F5F5), child: Column( children: [ _buildSearchBar(), _buildEmployeeDropdown(context), _buildTopBalance(), _buildPaymentList(), ], ), ), ) // ---------------- LANDSCAPE (FIXED) ---------------- : SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Container( width: double.infinity, color: const Color(0xFFF5F5F5), // ❗ Removed IntrinsicHeight // ❗ Removed ConstrainedBox // Dropdown can now open freely child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildSearchBar(), _buildEmployeeDropdown( context), // now overlay works _buildTopBalance(), _buildPaymentList(), ], ), ), ), ), ); }, ), ), ); } // ---------------- AppBar ---------------- PreferredSizeWidget _buildAppBar() { return PreferredSize( preferredSize: const Size.fromHeight(72), child: AppBar( backgroundColor: const Color(0xFFF5F5F5), elevation: 0.5, automaticallyImplyLeading: false, titleSpacing: 0, title: Padding( padding: MySpacing.xy(16, 0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offNamed('/dashboard/finance'), ), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ MyText.titleLarge( 'Advance Payments', fontWeight: 700, color: Colors.black, ), MySpacing.height(2), GetBuilder( builder: (_) { final name = projectController.selectedProject?.name ?? 'Select Project'; return Row( children: [ const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( name, fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.grey[700], ), ), ], ); }, ), ], ), ), ], ), ), ), ); } // ---------------- Search ---------------- Widget _buildSearchBar() { return Container( color: Colors.grey[100], padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ Expanded( child: SizedBox( height: 38, child: TextField( controller: _searchCtrl, focusNode: _searchFocus, onTap: () { Future.delayed(const Duration(milliseconds: 50), () { if (mounted) { FocusScope.of(context).requestFocus(_searchFocus); } }); }, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0), prefixIcon: const Icon(Icons.search, size: 20, color: Colors.grey), hintText: 'Search Employee...', filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: contentTheme.primary, width: 1.5), ), ), ), ), ), ], ), ); } // ---------------- Employee Dropdown ---------------- Widget _buildEmployeeDropdown(BuildContext context) { return Obx(() { if (controller.employees.isEmpty || controller.selectedEmployee.value != null) { return const SizedBox.shrink(); } return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 3)) ], ), constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.4, ), child: ListView.separated( padding: const EdgeInsets.symmetric(vertical: 6), shrinkWrap: true, physics: const BouncingScrollPhysics(), itemCount: controller.employees.length, separatorBuilder: (_, __) => Divider(height: 1, color: Colors.grey.shade200), itemBuilder: (_, i) => _buildEmployeeItem(controller.employees[i]), ), ); }); } Widget _buildEmployeeItem(dynamic e) { return InkWell( onTap: () { controller.selectEmployee(e); _searchCtrl ..text = e.name ..selection = TextSelection.fromPosition( TextPosition(offset: e.name.length), ); FocusScope.of(context).unfocus(); SystemChannels.textInput.invokeMethod('TextInput.hide'); controller.employees.clear(); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ CircleAvatar( radius: 18, backgroundColor: _avatarColorFor(e.name), child: Text( _initials(e.firstName, e.lastName), style: const TextStyle(color: Colors.white), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(e.name, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Colors.black87)), if (e.email.isNotEmpty) Text(e.email, style: TextStyle( fontSize: 13, color: Colors.grey.shade600)), ], ), ), ], ), ), ); } // ---------------- Current Balance ---------------- Widget _buildTopBalance() { return Obx(() { if (controller.payments.isEmpty) return const SizedBox.shrink(); final bal = controller.payments.first.balance.truncate(); return Container( width: double.infinity, color: Colors.grey[100], padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ const Text( "Current Balance : ", style: TextStyle( fontWeight: FontWeight.w600, color: Colors.green, fontSize: 22, ), ), Text( "₹${_formatAmount(bal)}", style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.green, fontSize: 22, ), ), ], ), ); }); } // ---------------- Payments List ---------------- Widget _buildPaymentList() { return Obx(() { if (controller.isLoading.value) { return const Padding( padding: EdgeInsets.only(top: 100), child: Center(child: CircularProgressIndicator(color: Colors.blue)), ); } // ✅ No employee selected yet if (controller.selectedEmployee.value == null) { return const Padding( padding: EdgeInsets.only(top: 100), child: Center(child: Text("Please select an Employee")), ); } // ✅ Employee selected but no payments found if (controller.payments.isEmpty) { return const Padding( padding: EdgeInsets.only(top: 100), child: Center( child: Text("No advance payment transactions found."), ), ); } // ✅ Payments available return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 6), itemCount: controller.payments.length, itemBuilder: (context, index) => _buildPaymentItem(controller.payments[index]), ); }); } // ---------------- Payment Item ---------------- Widget _buildPaymentItem(dynamic item) { final dateStr = (item.date ?? '').toString(); DateTime? parsedDate; try { parsedDate = DateTime.parse(dateStr); } catch (_) {} final formattedDate = parsedDate != null ? DateFormat('dd MMM yyyy').format(parsedDate) : (dateStr.isNotEmpty ? dateStr : '—'); final project = item.name ?? ''; final desc = item.title ?? ''; final amount = (item.amount ?? 0).toDouble(); final isCredit = amount >= 0; final accentColor = isCredit ? Colors.green.shade700 : Colors.red.shade700; return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: Colors.grey[100], border: Border( bottom: BorderSide(color: Color(0xFFE0E0E0), width: 0.9), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( formattedDate, style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], ), const SizedBox(height: 4), Text( project.isNotEmpty ? project : 'No Project', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Colors.black, ), ), const SizedBox(height: 4), Text( desc.isNotEmpty ? desc : 'No Details', maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, color: Colors.grey.shade700, ), ), ], ), ), const SizedBox(width: 8), Text( "${isCredit ? '+' : '-'} ₹${_formatAmount(amount)}", style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: accentColor, ), ), ], ), ); } // ---------------- Utilities ---------------- String _initials(String? firstName, [String? lastName]) { if ((firstName?.isEmpty ?? true) && (lastName?.isEmpty ?? true)) return '?'; return ((firstName?.isNotEmpty == true ? firstName![0] : '') + (lastName?.isNotEmpty == true ? lastName![0] : '')) .toUpperCase(); } String _formatAmount(num amount) { final format = NumberFormat('#,##,###.##', 'en_IN'); return format.format(amount); } static Color _avatarColorFor(String name) { final colors = [ Colors.green, Colors.indigo, Colors.orange, Colors.blueGrey, Colors.deepPurple, Colors.teal, Colors.amber, ]; final hash = name.codeUnits.fold(0, (p, e) => p + e); return colors[hash % colors.length]; } }