From 2347ec43a45529f13f351858039f4141d6a691fd Mon Sep 17 00:00:00 2001 From: "sanjeev.p" Date: Sat, 4 Apr 2026 17:28:14 +0530 Subject: [PATCH] FIX_bug --- .../screens/payout/payout_details.dart | 220 ++++++++++++++---- 1 file changed, 176 insertions(+), 44 deletions(-) diff --git a/lib/presentation/screens/payout/payout_details.dart b/lib/presentation/screens/payout/payout_details.dart index c9c3916..fd0b252 100644 --- a/lib/presentation/screens/payout/payout_details.dart +++ b/lib/presentation/screens/payout/payout_details.dart @@ -25,13 +25,23 @@ class _PayOutDetailsState extends ConsumerState { static final List _commissionInputFormatter = [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')), ]; + static const int _utrMinLength = 12; + static const int _utrMaxLength = 22; + /// Alphanumeric only, length validated on submit (12–22). + static final RegExp _utrFullValuePattern = + RegExp(r'^[a-zA-Z0-9]{12,22}$'); static final List _utrInputFormatter = [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')), + LengthLimitingTextInputFormatter(_utrMaxLength), ]; + + static bool _isValidUtr(String raw) => + _utrFullValuePattern.hasMatch(raw.trim()); // -------------------------- // Invoice Fields // -------------------------- final _formKey = GlobalKey(); + final _utrFormKey = GlobalKey(); // final TextEditingController invoiceNoController; DateTime invoiceDate = DateTime.now(); // dynamic selectedAgentId; @@ -83,6 +93,8 @@ class _PayOutDetailsState extends ConsumerState { bool isLoading = false; bool hasFetchedTableData = false; final Set _inlineSavingIds = {}; + /// Per-policy payout editors so [setState] rebuilds do not reset focus/text. + final Map _commissionControllers = {}; late ApiService apiService; dynamic managerId; dynamic userId; @@ -114,6 +126,89 @@ class _PayOutDetailsState extends ConsumerState { }); } + int _policyRowId(Map p) { + return int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; + } + + void _rebuildCommissionControllers(List> policies) { + for (final c in _commissionControllers.values) { + c.dispose(); + } + _commissionControllers.clear(); + for (final p in policies) { + final id = _policyRowId(p); + if (id == 0) continue; + _commissionControllers[id] = TextEditingController( + text: (p["commission_amount"] ?? '').toString(), + ); + } + } + + void _disposeCommissionControllers() { + for (final c in _commissionControllers.values) { + c.dispose(); + } + _commissionControllers.clear(); + } + + TextEditingController? _commissionControllerFor(Map p) { + final id = _policyRowId(p); + if (id == 0) return null; + return _commissionControllers[id]; + } + + Widget _commissionAmountField( + Map p, + TextEditingController? controller, + ) { + final fieldKey = ValueKey('commission_${p["id"] ?? p["policy_id"]}'); + final decoration = const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 6, + vertical: 6, + ), + border: OutlineInputBorder(), + ); + const fieldStyle = TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Color(0xFF009B77), + ); + void onChanged(String value) { + p["commission_amount"] = value.trim(); + _calculateTotalCommission(); + } + + if (controller != null) { + return TextFormField( + key: fieldKey, + controller: controller, + style: fieldStyle, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: _commissionInputFormatter, + decoration: decoration, + onChanged: onChanged, + onFieldSubmitted: (value) => _updateInlineCommission(p, value), + ); + } + return TextFormField( + key: fieldKey, + initialValue: (p["commission_amount"] ?? '').toString(), + style: fieldStyle, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: _commissionInputFormatter, + decoration: decoration, + onChanged: onChanged, + onFieldSubmitted: (value) => _updateInlineCommission(p, value), + ); + } + + @override + void dispose() { + _disposeCommissionControllers(); + super.dispose(); + } void filterPolicyData(String query) { final lowerQuery = query.toLowerCase(); @@ -167,6 +262,7 @@ class _PayOutDetailsState extends ConsumerState { totalPolicies = ''; totalCommission = ''; filteredPolicies = []; + _disposeCommissionControllers(); hasFetchedTableData = false; // dropDownKeyPartner.currentState?.clear(); }); @@ -302,6 +398,7 @@ class _PayOutDetailsState extends ConsumerState { print('PD =>API Data response - $response'); setState(() { filteredPolicies = List>.from(response['data']); + _rebuildCommissionControllers(filteredPolicies); hasFetchedTableData = true; print('PD =>API Data - $filteredPolicies'); // filteredPolicies = allPolicies.where((p) { @@ -315,6 +412,7 @@ class _PayOutDetailsState extends ConsumerState { } else { setState(() { filteredPolicies = []; + _rebuildCommissionControllers(filteredPolicies); hasFetchedTableData = true; }); } @@ -322,6 +420,7 @@ class _PayOutDetailsState extends ConsumerState { print('PD =>Exception occurred: $e'); setState(() { filteredPolicies = []; + _rebuildCommissionControllers(filteredPolicies); hasFetchedTableData = false; }); } finally { @@ -367,6 +466,11 @@ class _PayOutDetailsState extends ConsumerState { return; } + final utrFormState = _utrFormKey.currentState; + if (utrFormState != null && !utrFormState.validate()) { + return; + } + final String utrInput = (controllers['utrNumber']?.text ?? '').trim(); if (utrInput.isEmpty) { ToastHelper.showWarningToast( @@ -375,6 +479,14 @@ class _PayOutDetailsState extends ConsumerState { ); return; } + if (!_isValidUtr(utrInput)) { + ToastHelper.showWarningToast( + context, + "UTR must be $_utrMinLength–$_utrMaxLength letters or numbers only, " + "with no spaces or special characters.", + ); + return; + } // Format dates for API final String formattedInvoiceDate = DateFormat( @@ -822,11 +934,11 @@ class _PayOutDetailsState extends ConsumerState { color: Color(0xFFf9fafb), // color: Colors.white, child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ // ---------------- SELECTED COUNT ---------------- Row( - // crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( // selectedPolicies.length.toString(), @@ -883,7 +995,9 @@ class _PayOutDetailsState extends ConsumerState { buildUTRNumber(context), SizedBox(width: 20), // ---------------- BUTTONS ---------------- - Row( + Padding( + padding: const EdgeInsets.only(top: 2), + child: Row( children: [ ElevatedButton( onPressed: saveInvoice, @@ -917,6 +1031,7 @@ class _PayOutDetailsState extends ConsumerState { ), ], ), + ), ], ), ), @@ -1022,7 +1137,6 @@ class _PayOutDetailsState extends ConsumerState { final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; final bool isSelected = selectedPolicies.contains(id); - return InkWell( onTap: () { setState(() { @@ -1087,35 +1201,9 @@ class _PayOutDetailsState extends ConsumerState { ), const SizedBox(width: 2), Expanded( - child: TextFormField( - key: ValueKey( - 'commission_${p["id"] ?? p["policy_id"]}', - ), - initialValue: (p["commission_amount"] ?? '') - .toString(), - style: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: Color(0xFF009B77), - ), - keyboardType: const TextInputType.numberWithOptions( - decimal: true, - ), - inputFormatters: _commissionInputFormatter, - decoration: const InputDecoration( - isDense: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 6, - vertical: 6, - ), - border: OutlineInputBorder(), - ), - onChanged: (value) { - p["commission_amount"] = value.trim(); - _calculateTotalCommission(); - }, - onFieldSubmitted: (value) => - _updateInlineCommission(p, value), + child: _commissionAmountField( + p, + _commissionControllerFor(p), ), ), ], @@ -1186,6 +1274,14 @@ class _PayOutDetailsState extends ConsumerState { ); if (response['status'] == 'success' || response['status'] == 200) { row['commission_amount'] = trimmed; + final cid = _policyRowId(row); + final ctrl = _commissionControllers[cid]; + if (ctrl != null && ctrl.text != trimmed) { + ctrl.value = TextEditingValue( + text: trimmed, + selection: TextSelection.collapsed(offset: trimmed.length), + ); + } _calculateTotalCommission(); } else { ToastHelper.showErrorToast( @@ -1237,20 +1333,56 @@ class _PayOutDetailsState extends ConsumerState { } Widget buildUTRNumber(BuildContext context) { - + final fieldWidth = MediaQuery.of(context).size.width * 0.15; + final utrDecoration = commonInputDecoration( + hint: 'UTR ($_utrMinLength–$_utrMaxLength chars)', + ).copyWith( + hintStyle: GoogleFonts.poppins( + fontSize: 9, + fontWeight: FontWeight.w400, + color: const Color(0xFF9CA3AF), + ), + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + errorStyle: GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w500, + color: const Color(0xFFB91C1C), + height: 1.25, + ), + errorMaxLines: 3, + ); + return Row( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('UTR Number', style: _textStyle), - SizedBox(width: 5), + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text('UTR Number', style: _textStyle), + ), + const SizedBox(width: 5), SizedBox( - height: 35, - width: MediaQuery.of(context).size.width * 0.15, - child: TextFormField( - controller: controllers['utrNumber']!, - inputFormatters: _utrInputFormatter, - decoration: commonInputDecoration(hint: 'UTR Number'), - ), + width: fieldWidth.clamp(160.0, 280.0), + child: Form( + key: _utrFormKey, + child: TextFormField( + controller: controllers['utrNumber']!, + keyboardType: TextInputType.text, + autocorrect: false, + inputFormatters: _utrInputFormatter, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) { + final v = (value ?? '').trim(); + if (v.isEmpty) return null; + if (!_isValidUtr(v)) { + return 'Use $_utrMinLength–$_utrMaxLength letters or numbers only. ' + 'No spaces or symbols.'; + } + return null; + }, + decoration: utrDecoration, + ), + ), ), ], );