From 2d98e666a947e36d898c49654ad5c3af2cb2c25c Mon Sep 17 00:00:00 2001 From: "sanjeev.p" Date: Sat, 4 Apr 2026 16:57:19 +0530 Subject: [PATCH] FIX_Bug --- .../UserManagement/Agent/agent_details.dart | 52 ++++++++- .../dashboard/partner_portal_dashboard.dart | 110 ++++++++++-------- .../screens/payout/payout_details.dart | 2 +- .../themes/indicators/text_field_theme.dart | 12 +- 4 files changed, 118 insertions(+), 58 deletions(-) diff --git a/lib/presentation/screens/UserManagement/Agent/agent_details.dart b/lib/presentation/screens/UserManagement/Agent/agent_details.dart index 89ba391..d9a4c43 100644 --- a/lib/presentation/screens/UserManagement/Agent/agent_details.dart +++ b/lib/presentation/screens/UserManagement/Agent/agent_details.dart @@ -103,6 +103,21 @@ class _AgentDetailsScreenState extends ConsumerState { _bootstrap(); } + /// After create, [context.go] to `/agentDetails/:id` can update [widget.agentId] while + /// reusing this [State] — [initState] does not run again, so we must load the new id here. + @override + void didUpdateWidget(covariant AgentDetailsScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.agentId != widget.agentId && !widget.isCreate) { + _reloadAgentForRouteIdChange(); + } + } + + Future _reloadAgentForRouteIdChange() async { + await _loadAgent(); + if (mounted) setState(() {}); + } + Future _bootstrap() async { _token = await AuthService.getToken(); await _loadSalesExecutives(); @@ -186,7 +201,11 @@ class _AgentDetailsScreenState extends ConsumerState { return; } final data = Map.from(res['data'] as Map); - _loadedAgentPk = data['id']?.toString(); + final pk = data['id'] ?? data['agent_id']; + _loadedAgentPk = pk?.toString().trim(); + if (_loadedAgentPk != null && _loadedAgentPk!.isEmpty) { + _loadedAgentPk = null; + } _nameCtrl.text = (data['name'] ?? '').toString(); _emailCtrl.text = (data['email'] ?? '').toString(); @@ -317,6 +336,30 @@ class _AgentDetailsScreenState extends ConsumerState { } } + /// Backend may return the new row id under [data.id], [data.agent_id], or top-level keys. + String? _newAgentIdFromCreateBody(dynamic body) { + if (body is! Map) return null; + final m = Map.from(body); + final data = m['data']; + if (data is Map) { + final dm = Map.from(data); + for (final key in ['agent_id', 'id', 'agentId']) { + final v = dm[key]; + final s = v?.toString().trim(); + if (s != null && s.isNotEmpty && s != 'null') return s; + } + } else if (data != null && data is! List) { + final s = data.toString().trim(); + if (s.isNotEmpty && s != 'null') return s; + } + for (final key in ['agent_id', 'id']) { + final v = m[key]; + final s = v?.toString().trim(); + if (s != null && s.isNotEmpty && s != 'null') return s; + } + return null; + } + Future _saveRowRetention(int vehicleTypeId) async { if (!_retentionTableEnabled) { ToastHelper.showWarningToast( @@ -466,12 +509,9 @@ class _AgentDetailsScreenState extends ConsumerState { return; } if (!isUpdate && mounted) { - final data = body['data']; - String? newId; - if (data is Map && data['agent_id'] != null) { - newId = data['agent_id'].toString(); - } + final newId = _newAgentIdFromCreateBody(body); if (newId != null && newId.isNotEmpty) { + setState(() => _loadedAgentPk = newId); context.go(AppRoutes.agentDetailsFor(newId)); return; } diff --git a/lib/presentation/screens/dashboard/partner_portal_dashboard.dart b/lib/presentation/screens/dashboard/partner_portal_dashboard.dart index 9382d2e..558c7c3 100644 --- a/lib/presentation/screens/dashboard/partner_portal_dashboard.dart +++ b/lib/presentation/screens/dashboard/partner_portal_dashboard.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; import '../../../../core/services/api_service.dart'; import '../../providers/manager_provider.dart'; @@ -137,12 +138,13 @@ class _State extends ConsumerState { double _d(dynamic v) => double.tryParse(v?.toString() ?? '0') ?? 0; + /// Indian-grouped full rupee amount (no L/Cr/K shorthand). String _inr(dynamic v) { final n = _d(v); - if (n >= 10000000) return 'Rs.${(n/10000000).toStringAsFixed(1)}Cr'; - if (n >= 100000) return 'Rs.${(n/100000).toStringAsFixed(1)}L'; - if (n >= 1000) return 'Rs.${(n/1000).toStringAsFixed(1)}K'; - return 'Rs.${n.toStringAsFixed(0)}'; + if (n == n.roundToDouble()) { + return 'Rs.${NumberFormat('#,##,###', 'en_IN').format(n.toInt())}'; + } + return 'Rs.${NumberFormat('#,##,##0.00', 'en_IN').format(n)}'; } String _initials(String name) { final p = name.trim().split(' '); @@ -218,8 +220,8 @@ class _State extends ConsumerState { // ───────────────────────────────────────────────────────────────────────────── // PARTNER PROFILE CARD -// Fields: agent_name, agent_code, mobile, email, manager_name, status -// All come from partnerDetails() → partner_agent table +// Fields: agent_name, agent_code, mobile, email, manager_name, manager_mobile, status +// All come from GET partner/{id}/details (partner_agent + manager join) // ───────────────────────────────────────────────────────────────────────────── class _PartnerProfileCard extends StatelessWidget { final Map details; final String initials; @@ -231,6 +233,8 @@ class _PartnerProfileCard extends StatelessWidget { final mobile = details['mobile'] ?? '--'; final email = details['email'] ?? '--'; final manager = details['manager_name'] ?? '--'; + final managerMobile = details['manager_mobile'] ?? '--'; + final managerEmail = details['manager_email'] ?? '--'; final status = details['status'] ?? 'Active'; return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), @@ -249,6 +253,8 @@ class _PartnerProfileCard extends StatelessWidget { _m('Mobile', mobile.toString()), _m('Email', email.toString()), _m('Manager', manager.toString()), + _m('Manager Mobile', managerMobile), + _m('Manager Email', managerEmail), ]), ])), const SizedBox(width: 12), @@ -438,20 +444,20 @@ class _RenewalAlertsCard extends StatelessWidget { children: [ Text('Renewal Alerts', style: _chartHeader), Text( - 'At-risk policies', + '', style: GoogleFonts.inter(fontSize: 9.5, color: const Color(0xFF8AABB5)), ), ], ), const Spacer(), - if (totalCount > 0) - Padding( - padding: const EdgeInsets.only(right: 8), - child: Text( - '$totalCount total', - style: GoogleFonts.inter(fontSize: 10, color: const Color(0xFF8AABB5)), - ), - ), + // if (totalCount > 0) + // Padding( + // padding: const EdgeInsets.only(right: 8), + // child: Text( + // '$totalCount total', + // style: GoogleFonts.inter(fontSize: 10, color: const Color(0xFF8AABB5)), + // ), + // ), Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( @@ -501,13 +507,13 @@ class _RenewalAlertsCard extends StatelessWidget { color: const Color(0xFFE3F9F8), child: Row( children: [ - SizedBox(width: 88, child: Text('Due', style: _tableHeadStyle)), + SizedBox(width: 40, child: Text('S.No', style: _tableHeadStyle)), Expanded(flex: 2, child: Text('Policy No.', style: _tableHeadStyle)), - Expanded(flex: 2, child: Text('Holder', style: _tableHeadStyle)), - Expanded(flex: 2, child: Text('Expires', style: _tableHeadStyle)), + Expanded(flex: 2, child: Text('Policy Holder Name', style: _tableHeadStyle)), + Expanded(flex: 2, child: Text('Expiry Date', style: _tableHeadStyle)), Expanded( flex: 2, - child: Text('Premium', textAlign: TextAlign.right, style: _tableHeadStyle), + child: Text('Premium Amount', textAlign: TextAlign.right, style: _tableHeadStyle), ), ], ), @@ -518,7 +524,11 @@ class _RenewalAlertsCard extends StatelessWidget { physics: const ClampingScrollPhysics(), itemCount: renewals.length, separatorBuilder: (_, __) => const Divider(height: 1, color: Color(0xFFD9F0ED)), - itemBuilder: (_, i) => _RenewalTableRow(renewal: renewals[i], inr: inr), + itemBuilder: (_, i) => _RenewalTableRow( + serialNo: i + 1, + renewal: renewals[i], + inr: inr, + ), ), ), ], @@ -527,52 +537,42 @@ class _RenewalAlertsCard extends StatelessWidget { } class _RenewalTableRow extends StatelessWidget { + final int serialNo; final Map renewal; final String Function(dynamic) inr; - const _RenewalTableRow({required this.renewal, required this.inr}); + const _RenewalTableRow({ + required this.serialNo, + required this.renewal, + required this.inr, + }); @override Widget build(BuildContext context) { final d = int.tryParse(renewal['days_left']?.toString() ?? '0') ?? 0; - Color chipBg, chipFg; + final Color chipFg; if (d <= 10) { - chipBg = const Color(0xFFFFEDED); chipFg = const Color(0xFFEF4444); } else if (d <= 20) { - chipBg = const Color(0xFFFEF0E7); chipFg = const Color(0xFFF97316); } else { - chipBg = const Color(0xFFF1F5F9); chipFg = const Color(0xFF64748B); } + final endDate = renewal['end_date']?.toString() ?? '--'; return SizedBox( height: _RenewalAlertsCard.kRowHeight, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( - width: 88, - child: Align( - alignment: Alignment.centerLeft, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), - decoration: BoxDecoration( - color: chipBg, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - '$d d', - style: GoogleFonts.inter( - fontSize: 9.5, - fontWeight: FontWeight.w700, - color: chipFg, - letterSpacing: 0.2, - ), - ), - ), + width: 40, + child: Text( + '$serialNo', + style: _tableData, + textAlign: TextAlign.center, ), ), Expanded( @@ -593,18 +593,32 @@ class _RenewalTableRow extends StatelessWidget { ), Expanded( flex: 2, - child: Text( - renewal['end_date']?.toString() ?? '--', - style: _tableData, + child: Text.rich( + TextSpan( + style: _tableData, + children: [ + TextSpan(text: endDate), + TextSpan( + text: ' ($d d)', + style: GoogleFonts.inter( + fontSize: 9, + fontWeight: FontWeight.w600, + color: chipFg, + ), + ), + ], + ), overflow: TextOverflow.ellipsis, ), ), Expanded( flex: 2, child: Text( - inr(renewal['premium'] ?? 0), + inr(renewal['premium'] ?? renewal['premium_amount'] ?? 0), style: _tableDataBold, textAlign: TextAlign.right, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), ), ], diff --git a/lib/presentation/screens/payout/payout_details.dart b/lib/presentation/screens/payout/payout_details.dart index 97082f1..c9c3916 100644 --- a/lib/presentation/screens/payout/payout_details.dart +++ b/lib/presentation/screens/payout/payout_details.dart @@ -1089,7 +1089,7 @@ class _PayOutDetailsState extends ConsumerState { Expanded( child: TextFormField( key: ValueKey( - 'commission_${p["policy_id"]}_${p["commission_amount"]}', + 'commission_${p["id"] ?? p["policy_id"]}', ), initialValue: (p["commission_amount"] ?? '') .toString(), diff --git a/lib/presentation/themes/indicators/text_field_theme.dart b/lib/presentation/themes/indicators/text_field_theme.dart index 43eb001..2de6bbc 100644 --- a/lib/presentation/themes/indicators/text_field_theme.dart +++ b/lib/presentation/themes/indicators/text_field_theme.dart @@ -5,6 +5,11 @@ import 'package:google_fonts/google_fonts.dart'; // import 'package:uae_stat/config/my_theme.dart'; +/// Characters allowed while typing an email (matches typical local-part + domain). +final List kEmailAddressInputFormatters = [ + FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9@._%+-]')), +]; + class ThemedFormField extends HookWidget { ThemedFormField({ super.key, @@ -202,9 +207,10 @@ class ThemedFormField extends HookWidget { maxLength: maxLength, // ✅ max length showCursor: isShowCursor ?? true, enableInteractiveSelection: isShowCursor ?? true, - inputFormatters: - inputFormatters ?? - [FilteringTextInputFormatter.allow(RegExp(r'[a-z A-Z]'))], + inputFormatters: inputFormatters ?? + (keyboardType == TextInputType.emailAddress + ? kEmailAddressInputFormatters + : null), // ✅ allow multiline if user sets maxLines / minLines minLines: (keyboardType == TextInputType.multiline) ? 3 : 1, maxLines: (keyboardType == TextInputType.multiline) ? null : 1,