This commit is contained in:
sanjeev.p 2026-04-04 17:28:14 +05:30
parent 2d98e666a9
commit 2347ec43a4

View File

@ -25,13 +25,23 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
static final List<TextInputFormatter> _commissionInputFormatter = [ static final List<TextInputFormatter> _commissionInputFormatter = [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')), FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
]; ];
static const int _utrMinLength = 12;
static const int _utrMaxLength = 22;
/// Alphanumeric only, length validated on submit (1222).
static final RegExp _utrFullValuePattern =
RegExp(r'^[a-zA-Z0-9]{12,22}$');
static final List<TextInputFormatter> _utrInputFormatter = [ static final List<TextInputFormatter> _utrInputFormatter = [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')), FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
LengthLimitingTextInputFormatter(_utrMaxLength),
]; ];
static bool _isValidUtr(String raw) =>
_utrFullValuePattern.hasMatch(raw.trim());
// -------------------------- // --------------------------
// Invoice Fields // Invoice Fields
// -------------------------- // --------------------------
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _utrFormKey = GlobalKey<FormState>();
// final TextEditingController invoiceNoController; // final TextEditingController invoiceNoController;
DateTime invoiceDate = DateTime.now(); DateTime invoiceDate = DateTime.now();
// dynamic selectedAgentId; // dynamic selectedAgentId;
@ -83,6 +93,8 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
bool isLoading = false; bool isLoading = false;
bool hasFetchedTableData = false; bool hasFetchedTableData = false;
final Set<int> _inlineSavingIds = <int>{}; final Set<int> _inlineSavingIds = <int>{};
/// Per-policy payout editors so [setState] rebuilds do not reset focus/text.
final Map<int, TextEditingController> _commissionControllers = {};
late ApiService apiService; late ApiService apiService;
dynamic managerId; dynamic managerId;
dynamic userId; dynamic userId;
@ -114,6 +126,89 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
}); });
} }
int _policyRowId(Map<String, dynamic> p) {
return int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
}
void _rebuildCommissionControllers(List<Map<String, dynamic>> 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<String, dynamic> p) {
final id = _policyRowId(p);
if (id == 0) return null;
return _commissionControllers[id];
}
Widget _commissionAmountField(
Map<String, dynamic> 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) { void filterPolicyData(String query) {
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
@ -167,6 +262,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
totalPolicies = ''; totalPolicies = '';
totalCommission = ''; totalCommission = '';
filteredPolicies = []; filteredPolicies = [];
_disposeCommissionControllers();
hasFetchedTableData = false; hasFetchedTableData = false;
// dropDownKeyPartner.currentState?.clear(); // dropDownKeyPartner.currentState?.clear();
}); });
@ -302,6 +398,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
print('PD =>API Data response - $response'); print('PD =>API Data response - $response');
setState(() { setState(() {
filteredPolicies = List<Map<String, dynamic>>.from(response['data']); filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
_rebuildCommissionControllers(filteredPolicies);
hasFetchedTableData = true; hasFetchedTableData = true;
print('PD =>API Data - $filteredPolicies'); print('PD =>API Data - $filteredPolicies');
// filteredPolicies = allPolicies.where((p) { // filteredPolicies = allPolicies.where((p) {
@ -315,6 +412,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
} else { } else {
setState(() { setState(() {
filteredPolicies = []; filteredPolicies = [];
_rebuildCommissionControllers(filteredPolicies);
hasFetchedTableData = true; hasFetchedTableData = true;
}); });
} }
@ -322,6 +420,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
print('PD =>Exception occurred: $e'); print('PD =>Exception occurred: $e');
setState(() { setState(() {
filteredPolicies = []; filteredPolicies = [];
_rebuildCommissionControllers(filteredPolicies);
hasFetchedTableData = false; hasFetchedTableData = false;
}); });
} finally { } finally {
@ -367,6 +466,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
return; return;
} }
final utrFormState = _utrFormKey.currentState;
if (utrFormState != null && !utrFormState.validate()) {
return;
}
final String utrInput = (controllers['utrNumber']?.text ?? '').trim(); final String utrInput = (controllers['utrNumber']?.text ?? '').trim();
if (utrInput.isEmpty) { if (utrInput.isEmpty) {
ToastHelper.showWarningToast( ToastHelper.showWarningToast(
@ -375,6 +479,14 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
); );
return; 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 // Format dates for API
final String formattedInvoiceDate = DateFormat( final String formattedInvoiceDate = DateFormat(
@ -822,11 +934,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
color: Color(0xFFf9fafb), color: Color(0xFFf9fafb),
// color: Colors.white, // color: Colors.white,
child: Row( child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// ---------------- SELECTED COUNT ---------------- // ---------------- SELECTED COUNT ----------------
Row( Row(
// crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
// selectedPolicies.length.toString(), // selectedPolicies.length.toString(),
@ -883,7 +995,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
buildUTRNumber(context), buildUTRNumber(context),
SizedBox(width: 20), SizedBox(width: 20),
// ---------------- BUTTONS ---------------- // ---------------- BUTTONS ----------------
Row( Padding(
padding: const EdgeInsets.only(top: 2),
child: Row(
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: saveInvoice, onPressed: saveInvoice,
@ -917,6 +1031,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
), ),
], ],
), ),
),
], ],
), ),
), ),
@ -1022,7 +1137,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
final bool isSelected = selectedPolicies.contains(id); final bool isSelected = selectedPolicies.contains(id);
return InkWell( return InkWell(
onTap: () { onTap: () {
setState(() { setState(() {
@ -1087,35 +1201,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
Expanded( Expanded(
child: TextFormField( child: _commissionAmountField(
key: ValueKey( p,
'commission_${p["id"] ?? p["policy_id"]}', _commissionControllerFor(p),
),
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),
), ),
), ),
], ],
@ -1186,6 +1274,14 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
); );
if (response['status'] == 'success' || response['status'] == 200) { if (response['status'] == 'success' || response['status'] == 200) {
row['commission_amount'] = trimmed; 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(); _calculateTotalCommission();
} else { } else {
ToastHelper.showErrorToast( ToastHelper.showErrorToast(
@ -1237,20 +1333,56 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
} }
Widget buildUTRNumber(BuildContext context) { 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( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('UTR Number', style: _textStyle), Padding(
SizedBox(width: 5), padding: const EdgeInsets.only(top: 10),
child: Text('UTR Number', style: _textStyle),
),
const SizedBox(width: 5),
SizedBox( SizedBox(
height: 35, width: fieldWidth.clamp(160.0, 280.0),
width: MediaQuery.of(context).size.width * 0.15, child: Form(
child: TextFormField( key: _utrFormKey,
controller: controllers['utrNumber']!, child: TextFormField(
inputFormatters: _utrInputFormatter, controller: controllers['utrNumber']!,
decoration: commonInputDecoration(hint: 'UTR Number'), 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,
),
),
), ),
], ],
); );