FIX_bug
This commit is contained in:
parent
2d98e666a9
commit
2347ec43a4
@ -25,13 +25,23 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
static final List<TextInputFormatter> _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<TextInputFormatter> _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<FormState>();
|
||||
final _utrFormKey = GlobalKey<FormState>();
|
||||
// final TextEditingController invoiceNoController;
|
||||
DateTime invoiceDate = DateTime.now();
|
||||
// dynamic selectedAgentId;
|
||||
@ -83,6 +93,8 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
bool isLoading = false;
|
||||
bool hasFetchedTableData = false;
|
||||
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;
|
||||
dynamic managerId;
|
||||
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) {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
@ -167,6 +262,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
totalPolicies = '';
|
||||
totalCommission = '';
|
||||
filteredPolicies = [];
|
||||
_disposeCommissionControllers();
|
||||
hasFetchedTableData = false;
|
||||
// dropDownKeyPartner.currentState?.clear();
|
||||
});
|
||||
@ -302,6 +398,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
print('PD =>API Data response - $response');
|
||||
setState(() {
|
||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
hasFetchedTableData = true;
|
||||
print('PD =>API Data - $filteredPolicies');
|
||||
// filteredPolicies = allPolicies.where((p) {
|
||||
@ -315,6 +412,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
} else {
|
||||
setState(() {
|
||||
filteredPolicies = [];
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
hasFetchedTableData = true;
|
||||
});
|
||||
}
|
||||
@ -322,6 +420,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
print('PD =>Exception occurred: $e');
|
||||
setState(() {
|
||||
filteredPolicies = [];
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
hasFetchedTableData = false;
|
||||
});
|
||||
} finally {
|
||||
@ -367,6 +466,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
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<PayOutDetails> {
|
||||
);
|
||||
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<PayOutDetails> {
|
||||
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<PayOutDetails> {
|
||||
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<PayOutDetails> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -1022,7 +1137,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
|
||||
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<PayOutDetails> {
|
||||
),
|
||||
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<PayOutDetails> {
|
||||
);
|
||||
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<PayOutDetails> {
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user