FIX_Tracker task

This commit is contained in:
sanjeev.p 2026-03-13 10:26:57 +05:30
parent df4d01ee6e
commit bf729885a3
17 changed files with 859 additions and 613 deletions

View File

@ -90,7 +90,7 @@ class _SplashScreenState extends State<SplashScreen> {
Future<String?> tokenRedirectLogic( Future<String?> tokenRedirectLogic(
BuildContext context, GoRouterState state) async { BuildContext context, GoRouterState state) async {
print('ABCDEFGH'); print('ABCDEFGH');
const allowedWithoutToken = [ const guestRoutes = [
'/login', '/login',
'/verify', '/verify',
'/mailVerify', '/mailVerify',
@ -98,17 +98,17 @@ Future<String?> tokenRedirectLogic(
'/splash', '/splash',
]; ];
final location = state.uri.toString();
// Allow login, verify, splash without token
if (allowedWithoutToken.contains(location)) return null;
// Check if token exists
// final hasToken = await TokenService.hasValidToken();
// if (!hasToken) return '/login';
final hasToken = await TokenService.hasValidToken(); final hasToken = await TokenService.hasValidToken();
print('hasToken : $hasToken'); print('hasToken : $hasToken');
if (!hasToken) { final location = state.matchedLocation;
// If user is already logged in, never allow returning to OTP/login screens.
if (hasToken && guestRoutes.contains(location)) {
return '/home';
}
// If not logged in and trying to access a protected route, force login.
if (!hasToken && !guestRoutes.contains(location)) {
print('!!!!hasToken : $hasToken'); print('!!!!hasToken : $hasToken');
await SessionManager().clear(); await SessionManager().clear();
// Show toast once // Show toast once

View File

@ -67,6 +67,12 @@ class _changesPasswordState extends State<changesPassword> {
final confirmPassword = confirmPasswordController.text.trim(); final confirmPassword = confirmPasswordController.text.trim();
try { try {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
if (!isPasswordValid) {
ToastHelper.showErrorToast(
context,
'Password must be 8+ characters with 1 letter, 1 number, and 1 special character');
return;
}
if (confirmPassword != newPassword) { if (confirmPassword != newPassword) {
ToastHelper.showErrorToast(context, 'Passwords do not match'); ToastHelper.showErrorToast(context, 'Passwords do not match');
return; return;
@ -196,7 +202,7 @@ class _changesPasswordState extends State<changesPassword> {
body: SingleChildScrollView( body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container( child: Container(
height: _size.height, constraints: BoxConstraints(minHeight: _size.height),
color: Colors.white, color: Colors.white,
child: Stack( child: Stack(
children: [ children: [
@ -283,7 +289,7 @@ class _changesPasswordState extends State<changesPassword> {
// ), // ),
Container( Container(
margin: marginInsets, margin: marginInsets,
alignment: Alignment.bottomCenter, alignment: Alignment.topCenter,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
@ -301,62 +307,13 @@ class _changesPasswordState extends State<changesPassword> {
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
if (!Responsive.isMobile(context) && // Removed desktop-only back icon and logo to eliminate extra top spacing
!Responsive.isTablet(context))
Row(
children: [
InkWell(
onTap: () {
context.go('/home');
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// if (!Responsive.isDesktop(context))
Icon(
Icons.chevron_left,
color: Color(0xFF000000),
size: 30,
),
SizedBox(
width: Responsive.isDesktop(context)
? 0
: 5),
],
),
),
Expanded(
flex: 12,
child: Align(
alignment: Alignment
.topLeft, // Always top-left
child: _size.width <= 1100
? Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
)
: _size.width > 1100
? Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
)
: Image.asset(
'assets/nhance_app_logo.png',
width: 150,
height: 150,
),
),
),
],
),
SizedBox( SizedBox(
height: Responsive.isDesktop(context) height: Responsive.isDesktop(context)
? _size.height * 0.1 ? null
: 10, : 10,
), ),
SizedBox(height: 10), // SizedBox(height: 10),
Container( Container(
margin: Responsive.isDesktop(context) margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
@ -508,6 +465,25 @@ class _changesPasswordState extends State<changesPassword> {
}, },
), ),
), ),
validator: (value) {
final password = value ?? '';
if (password.isEmpty) {
return 'Please enter your new password';
}
if (password.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(r'[A-Za-z]').hasMatch(password)) {
return 'Password must include at least 1 letter';
}
if (!RegExp(r'\d').hasMatch(password)) {
return 'Password must include at least 1 number';
}
if (!RegExp(r'[@$!%*#?&]').hasMatch(password)) {
return 'Password must include at least 1 special character';
}
return null;
},
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -651,14 +627,14 @@ class _changesPasswordState extends State<changesPassword> {
SizedBox( SizedBox(
height: Responsive.isDesktop(context) height: Responsive.isDesktop(context)
? _size.height * 0.3 ? null
: _size.height * 0.2, : _size.height * 0.2,
), ),
// SizedBox( // SizedBox(
// height: _size.height * 0.1, // height: _size.height * 0.1,
// ), // ),
Container( Container(
alignment: Alignment.bottomCenter, alignment: Alignment.center,
padding: padding:
EdgeInsets.symmetric(vertical: 8), EdgeInsets.symmetric(vertical: 8),
child: RichText( child: RichText(

View File

@ -32,171 +32,216 @@
// import '../session/settingUpPinAndBiometric.dart'; // import '../session/settingUpPinAndBiometric.dart';
// import '../verify.dart'; // import '../verify.dart';
// //
// class AppRouter { // import 'package:flutter/foundation.dart';
// // static final SessionNotifier sessionNotifier = SessionNotifier(); import 'package:flutter/material.dart';
// static GoRouter createRouter() { import 'package:go_router/go_router.dart';
// return GoRouter( import 'package:flutter/foundation.dart';
// initialLocation: kIsWeb ? '/login' : '/splash', import 'package:shared_preferences/shared_preferences.dart';
// debugLogDiagnostics: true, import '../../main.dart';
// // refreshListenable: sessionNotifier, import '../email_verify.dart';
// redirect: (BuildContext context, GoRouterState state) { import '../enrollment/addons.dart';
// const allowedWithoutToken = [ import '../enrollment/empDetails.dart';
// '/login', import '../enrollment/empReview.dart';
// '/verify', import '../login.dart';
// '/mailVerify', import '../postEnrollment/chatbot.dart';
// '/pinPage', import '../postEnrollment/claimprocess.dart';
// '/splash', import '../postEnrollment/claims.dart';
// ]; import '../postEnrollment/faqs.dart';
// import '../postEnrollment/generalexclusionsdeductibles.dart';
// final location = state.uri.toString(); import '../postEnrollment/help.dart';
// if (allowedWithoutToken.contains(location)) return null; import '../postEnrollment/home.dart';
// import '../postEnrollment/planclaimsform.dart';
// // final hasToken = TokenService.hasValidTokenSync(); import '../postEnrollment/policies.dart';
// // if (!hasToken) return '/login'; import '../postEnrollment/privacypolicy.dart';
// import '../postEnrollment/profile.dart';
// // if (!kIsWeb && import '../postEnrollment/raisedTicketList.dart';
// // SessionManager().prefs?.getString('is_mpin_skipped') == '0' && import '../postEnrollment/retailClaimForm.dart';
// // location != '/pinPage') { import '../postEnrollment/termsofuse.dart';
// // return '/pinPage'; import '../postEnrollment/tickets.dart';
// // } import '../postEnrollment/tickettracklist.dart';
// import '../postEnrollment/wellness.dart';
// return null; import '../postEnrollment/wellness_web_view.dart';
// }, import '../service/SessionManager.dart';
// import '../service/TokenService.dart';
// import '../session/SetPinBiometric.dart';
// import '../session/changePin.dart';
// routes: [ import '../session/settingUpPinAndBiometric.dart';
// // if (!kIsWeb) import '../verify.dart';
// // GoRoute(
// // path: '/splash', class AppRouter {
// // builder: (context, state) => SplashScreen(), // static final SessionNotifier sessionNotifier = SessionNotifier();
// // ), static GoRouter createRouter() {
// GoRoute( return GoRouter(
// path: '/login', // On web, respect the browser URL if present; otherwise fall back to login.
// builder: (context, state) => login(), initialLocation: kIsWeb ? '/login' : '/splash',
// ), debugLogDiagnostics: true,
// GoRoute( redirect: (BuildContext context, GoRouterState state) async {
// path: '/mailVerify', const allowedWithoutToken = [
// builder: (context, state) { '/login',
// final email = state.extra as String; '/verify',
// return MyEmailVerify(email: email); '/mailVerify',
// }, '/pinPage',
// ), '/splash',
// GoRoute( ];
// path: '/verify',
// builder: (context, state) { // Always work with the matched path (ignores query params).
// final args = state.extra as Map<String, dynamic>; final location = state.matchedLocation;
// return MyVerify( final hasToken = await TokenService.hasValidToken();
// verificationId: args['verificationId'] as String,
// mobileNumber: args['mobileNumber'] as String, final isGuestRoute = allowedWithoutToken.contains(location);
// resendToken: args['resendToken'],
// onResendCode: args['onResendCode'] as Function(String, int?), if (hasToken && isGuestRoute) {
// ); // If a logged-in user tries to access a guest route, redirect to home
// }, return '/home';
// ), }
// GoRoute(
// path: '/mailVerify', if (!hasToken && !isGuestRoute) {
// builder: (context, state) => MyVerify( // If a guest user tries to access a protected route, redirect to login
// verificationId: '', return '/login';
// mobileNumber: '', }
// resendToken: null,
// onResendCode: (String, int) {}, return null; // No redirect needed
// ), },
// ), routes: [
// GoRoute( if (!kIsWeb)
// path: '/home', GoRoute(
// builder: (context, state) => Home(), path: '/splash',
// ), builder: (context, state) => const SplashScreen(),
// GoRoute( ),
// path: '/pinSettingPage', GoRoute(
// builder: (context, state) => pinSettingPage(), path: '/login',
// ), builder: (context, state) => const login(),
// GoRoute( ),
// path: '/pinPage', GoRoute(
// builder: (context, state) => pinPage(), path: '/mailVerify',
// ), builder: (context, state) {
// GoRoute( final data = state.extra as Map<String, dynamic>;
// path: '/changePin', final type = data['type'] as String;
// builder: (context, state) => changePin(), final value = data['value'] as String;
// ), return MyEmailVerify(type: type, value: value);
// GoRoute( },
// path: '/claimprocess', ),
// builder: (context, state) => claimprocess(), // Legacy Firebase-phone OTP route is commented out in verify.dart,
// ), // so we do not expose /verify from GoRouter anymore.
// GoRoute( GoRoute(
// path: '/policies', path: '/home',
// builder: (context, state) => policies(), builder: (context, state) => Home(),
// ), ),
// GoRoute( GoRoute(
// path: '/claims', path: '/pinSettingPage',
// builder: (context, state) => claims(), builder: (context, state) => pinSettingPage(),
// ), ),
// GoRoute( GoRoute(
// path: '/profile', path: '/pinPage',
// builder: (context, state) => profile(), builder: (context, state) => pinPage(),
// ), ),
// GoRoute( GoRoute(
// path: '/help', path: '/changePin',
// builder: (context, state) => help(), builder: (context, state) => changePin(),
// ), ),
// GoRoute( GoRoute(
// path: '/wellness', path: '/claimprocess',
// builder: (context, state) => wellness(), builder: (context, state) => claimprocess(),
// ), ),
// GoRoute( GoRoute(
// path: '/planclaimsform', path: '/policies',
// builder: (context, state) => planclaimsform(), builder: (context, state) {
// ), final arguments =
// GoRoute( state.extra as Map<String, dynamic>?; // optional args
// path: '/privacypolicy', return policies(arguments: arguments);
// builder: (context, state) => privacypolicy(), },
// ), ),
// GoRoute( GoRoute(
// path: '/termsofuse', path: '/claims',
// builder: (context, state) => termsofuse(), builder: (context, state) {
// ), final int tabIndex = state.extra as int? ?? 0;
// GoRoute( return claims(initialTab: tabIndex);
// path: '/generalExclusionsDeductibles', },
// builder: (context, state) => generalExclusionsDeductibles(), ),
// ), GoRoute(
// GoRoute( path: '/profile',
// path: '/chatbot()', builder: (context, state) => profile(),
// builder: (context, state) => chatbot(), ),
// ), GoRoute(
// GoRoute( path: '/help',
// path: '/chatbot()', builder: (context, state) => help(),
// builder: (context, state) => chatbot(), ),
// ), GoRoute(
// GoRoute( path: '/wellness',
// path: '/tickettracklist', builder: (context, state) => wellness(),
// builder: (context, state) => tickettracklist( ),
// ticketID: "", GoRoute(
// ), path: '/privacypolicy',
// ), builder: (context, state) => privacypolicy(),
// GoRoute( ),
// path: '/empDetails()', GoRoute(
// builder: (context, state) => empDetails(), path: '/termsofuse',
// ), builder: (context, state) => termsofuse(),
// GoRoute( ),
// path: '/addOnsDetails()', GoRoute(
// builder: (context, state) => addOnsDetails(), path: '/generalExclusionsDeductibles',
// ), builder: (context, state) => generalExclusionsDeductibles(),
// GoRoute( ),
// path: '/empReviewDetails()', GoRoute(
// builder: (context, state) => empReviewDetails(), path: '/planclaimsform',
// ), builder: (context, state) {
// GoRoute( final details = state.extra as Map<String, dynamic>?;
// path: '/tickets()', return planclaimsform(details: details);
// builder: (context, state) => tickets(), },
// ), ),
// GoRoute( GoRoute(
// path: '/raisedTicketHistory()', path: '/retailClaimForm',
// builder: (context, state) => raisedTicketHistory(), builder: (context, state) {
// ), final details = state.extra as Map<String, dynamic>?;
// ], return retailClaimForm(details: details);
// ); },
// } ),
// } GoRoute(
path: '/raisedTicketHistory',
builder: (context, state) => raisedTicketHistory(),
),
GoRoute(
path: '/tickettracklist/:ticketID',
builder: (context, state) {
final ticketID = state.pathParameters['ticketID']!;
return tickettracklist(ticketID: ticketID);
},
),
GoRoute(
path: '/empDetails',
builder: (context, state) => empDetails(),
),
GoRoute(
path: '/faqs',
builder: (context, state) => faqs(),
),
GoRoute(
path: '/addOnsDetails',
builder: (context, state) => addOnsDetails(),
),
GoRoute(
path: '/empReviewDetails',
builder: (context, state) => empReviewDetails(),
),
GoRoute(
path: '/tickets',
builder: (context, state) => tickets(),
),
GoRoute(
path: '/chatbot',
builder: (context, state) => chatbot(),
),
GoRoute(
path: '/wellnessWebView',
builder: (context, state) {
final url = state.extra as String;
return WellnessWebView(url: url);
},
),
],
);
}
}
// //
// // // //
// // class AppRouter { // // class AppRouter {

View File

@ -474,7 +474,8 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
session.empClientBranchId == null || session.empClientBranchId!.isEmpty) { session.empClientBranchId == null || session.empClientBranchId!.isEmpty) {
prefs.setBool('isRetailLoggedIn', true); prefs.setBool('isRetailLoggedIn', true);
} }
context.go('/home'); // context.go('/home');
context.replace('/home');
} }
// if (emp_status == 'enrolled' || emp_status == 'active') { // if (emp_status == 'enrolled' || emp_status == 'active') {
// context.go('/home'); // context.go('/home');
@ -649,10 +650,12 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
if (_postToken != null && _postToken.isNotEmpty) { if (_postToken != null && _postToken.isNotEmpty) {
ToastHelper.showSuccessToast(context, 'Successfully Logged In'); ToastHelper.showSuccessToast(context, 'Successfully Logged In');
context.go('/home'); context.replace('/home');
// context.go('/home');
// Navigator.pushReplacementNamed(context, 'home'); // Navigator.pushReplacementNamed(context, 'home');
} else { } else {
context.go('/empDetails'); context.replace('/empDetails');
// context.go('/empDetails');
// Navigator.pushReplacementNamed(context, 'empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails');
} }
@ -703,10 +706,12 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(response.body); Map<String, dynamic> data = json.decode(response.body);
if(data['status'] == 'success') { if(data['status'] == 'success') {
context.go('/${route}'); context.replace('/${route}');
// context.go('/${route}');
} else if(data['status'] == 'failed'){ } else if(data['status'] == 'failed'){
print('setPassword'); print('setPassword');
context.goNamed( // goNamed
context.replaceNamed(
'setPassword', 'setPassword',
queryParameters: { queryParameters: {
'email': email_id, 'email': email_id,
@ -929,7 +934,7 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
), ),
Container( Container(
margin: marginInsets, margin: marginInsets,
alignment: Alignment.bottomCenter, alignment: Alignment.center,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
@ -1269,14 +1274,14 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
// ), // ),
SizedBox( SizedBox(
height: Responsive.isDesktop(context) height: Responsive.isDesktop(context)
? _size.height * 0.4 ? _size.height * 0.2
: _size.height * 0.3, : _size.height * 0.15,
), ),
// SizedBox( // SizedBox(
// height: _size.height * 0.1, // height: _size.height * 0.1,
// ), // ),
Container( Container(
alignment: Alignment.bottomCenter, alignment: Alignment.center,
padding: padding:
EdgeInsets.symmetric(vertical: 8), EdgeInsets.symmetric(vertical: 8),
child: RichText( child: RichText(

View File

@ -1433,10 +1433,9 @@ class _addOnsDetailsState extends State<addOnsDetails> {
Navigator.pushNamed(context, 'empDetails', Navigator.pushNamed(context, 'empDetails',
arguments: {'mobile': mobileNo}); arguments: {'mobile': mobileNo});
} else { } else {
context.go('/empDetails'); context.pop();
// Navigator.pushNamed(context, 'empDetails'); // Navigator.pushNamed(context, 'empDetails');
} } }
}
void checkSiTopUp() async { void checkSiTopUp() async {
List<Map<String, dynamic>> siData = [ List<Map<String, dynamic>> siData = [
@ -4520,8 +4519,7 @@ class _addOnsDetailsState extends State<addOnsDetails> {
checkDependentTopUp(); checkDependentTopUp();
sendAddonsGmcDependentToAPI(); sendAddonsGmcDependentToAPI();
} }
context.go('/empReviewDetails'); context.push('/empReviewDetails'); // Navigator.pushNamed(context,
// Navigator.pushNamed(context,
// 'empReviewDetails'); // 'empReviewDetails');
} }
: null, : null,

View File

@ -976,7 +976,7 @@ class _empDetailsState extends State<empDetails> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
context.go('/addOnsDetails'); context.push('/addOnsDetails');
// Navigator.pushNamed( // Navigator.pushNamed(
// context, 'addOnsDetails'); // context, 'addOnsDetails');
}, },
@ -2240,6 +2240,8 @@ class _empDetailsState extends State<empDetails> {
print('gmcFloaterTextDescription $gmcFloaterTextDescription'); print('gmcFloaterTextDescription $gmcFloaterTextDescription');
String gmcNotes = item['notes']; String gmcNotes = item['notes'];
print('gmcNotes $gmcNotes'); print('gmcNotes $gmcNotes');
String cleanedNotes = gmcNotes?.toString().toLowerCase().replaceAll(' ', '') ?? '';
print('gmcNotes cleaned: $cleanedNotes');
dynamic gmcECardDownload = item['eCardDownload']; dynamic gmcECardDownload = item['eCardDownload'];
print('gmcECardDownload $gmcECardDownload'); print('gmcECardDownload $gmcECardDownload');
bool gmcCopyDependenceDataEnable = item['copy_dependence_data_enable']; bool gmcCopyDependenceDataEnable = item['copy_dependence_data_enable'];
@ -2635,10 +2637,76 @@ class _empDetailsState extends State<empDetails> {
Column( Column(
children: familyFloaterContainers, children: familyFloaterContainers,
), ),
// Add Family Member Button HERE // Add Family Member Button:
// if (gmcOpenForEnrollment != 0 && gmcECardDownload == null)...[ // Show only when:
if(gmcOpenForEnrollment != 0 && gmcECardDownload == null && getFalseObjects.isNotEmpty)...[ // - enrollment is open
GestureDetector( // - e-card not generated
// - there are still dependants left to add
// - and the policy allows relationships beyond just "Self"
// if (gmcOpenForEnrollment != 0 &&
// gmcECardDownload == null &&
// getFalseObjects.isNotEmpty &&
// gmcRelationShip.any((rel) =>
// (rel?.toString().toLowerCase() ?? '') != 'self')) ...[
// Builder(builder: (context) {
// print("*** Condition is TRUE");
// GestureDetector(
// onTap: () {
// if (getFalseObjects.length == 0) {
// ToastHelper.showWarningToast(context, "No family member to add");
// return;
// }
// openAddFamilyMemberPopup(
// "Add",
// null,
// gmcClientPolicyId,
// gmcRelationShip,
// gmcSumInsured,
// );
// },
// child: Container(
// margin: EdgeInsets.only(top: 10),
// padding: EdgeInsets.all(15),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.black, width: 1),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(Icons.person_add, color: Colors.black),
// SizedBox(width: 10),
// Text(
// "Add Family Member",
// style: GoogleFonts.poppins(
// fontSize: Responsive.isDesktop(context) ? 20 : 16,
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// ),
// ),
// ),
// ],
if (cleanedNotes != 'allowedmembersself') ...<Widget>[
if (gmcOpenForEnrollment != 0 &&
gmcECardDownload == null &&
getFalseObjects.isNotEmpty &&
gmcRelationShip.any((rel) =>
(rel is Map
? rel['relationship']?.toString().toLowerCase()
: rel?.toString().toLowerCase() ?? '') != 'self')) ...<Widget>[
Builder(builder: (context) {
print("*** IF - Condition is TRUE");
print("*** gmcOpenForEnrollment: $gmcOpenForEnrollment");
print("*** gmcECardDownload: $gmcECardDownload");
print("*** getFalseObjects length: ${getFalseObjects.length}");
print("*** gmcRelationShip full: $gmcRelationShip");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return GestureDetector(
onTap: () { onTap: () {
if (getFalseObjects.length == 0) { if (getFalseObjects.length == 0) {
ToastHelper.showWarningToast(context, "No family member to add"); ToastHelper.showWarningToast(context, "No family member to add");
@ -2674,7 +2742,28 @@ class _empDetailsState extends State<empDetails> {
], ],
), ),
), ),
), );
}),
] else ...<Widget>[
Builder(builder: (context) {
print("*** ELSE - Condition is FALSE");
print("*** gmcOpenForEnrollment: $gmcOpenForEnrollment → pass: ${gmcOpenForEnrollment != 0}");
print("*** gmcECardDownload: $gmcECardDownload → pass: ${gmcECardDownload == null}");
print("*** getFalseObjects length: ${getFalseObjects.length} → pass: ${getFalseObjects.isNotEmpty}");
print("*** gmcRelationShip full list: $gmcRelationShip");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return SizedBox.shrink(); // no widget shown in else
}),
],
]else ...<Widget>[
Builder(builder: (context) {
print("*** OUTER ELSE - Condition is FALSE");
print("*** gmcNotes original: $gmcNotes");
print("*** gmcNotes cleaned: $cleanedNotes");
return SizedBox.shrink();
}),
], ],
SizedBox(height: 15), SizedBox(height: 15),
if (Responsive.isDesktop(context) && gmcIsValueValid) if (Responsive.isDesktop(context) && gmcIsValueValid)

View File

@ -252,7 +252,7 @@ class _loginState extends State<login> {
print('isEmailFieldVisible $isEmailFieldVisible'); print('isEmailFieldVisible $isEmailFieldVisible');
// prefs.setString('empEmail', emailController.text); // prefs.setString('empEmail', emailController.text);
print('${emailMobileController.text}'); print('${emailMobileController.text}');
context.push( context.go(
'/mailVerify', '/mailVerify',
extra: { extra: {
'type': 'email', 'type': 'email',
@ -261,7 +261,7 @@ class _loginState extends State<login> {
); );
} else { } else {
// _verifyPhoneNumber(); // _verifyPhoneNumber();
context.push( context.go(
'/mailVerify', '/mailVerify',
extra: { extra: {
'type': 'mobile', 'type': 'mobile',
@ -1097,7 +1097,7 @@ class _loginState extends State<login> {
child: Image.asset( child: Image.asset(
'assets/nhance_app_logo.png', 'assets/nhance_app_logo.png',
width: 150, width: 150,
height: 100, height: 150,
)), )),
), ),
if (!Responsive.isMobile(context) && if (!Responsive.isMobile(context) &&
@ -1148,7 +1148,7 @@ class _loginState extends State<login> {
), ),
Container( Container(
margin: marginInsets, margin: marginInsets,
alignment: Alignment.bottomCenter, alignment: Alignment.center,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
@ -1159,9 +1159,7 @@ class _loginState extends State<login> {
Expanded( Expanded(
flex: _size.width < 1100 ? 6 : 12, flex: _size.width < 1100 ? 6 : 12,
child: Container( child: Container(
margin: _size.width > 1100 margin: EdgeInsets.symmetric(horizontal: 20),
? EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(left: 0, right: 0),
child: Column( child: Column(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment.center,
@ -1677,119 +1675,114 @@ class _loginState extends State<login> {
}, },
), ),
), ),
if (otpFieldShow) ...[ if (otpFieldShow &&
SizedBox(height: 10), clickedForgotPassword) ...[
AnimatedSwitcher( const SizedBox(height: 10),
duration: const Duration( Column(
milliseconds: 500,
), // animation speed
switchInCurve:
Curves.easeInOutCirc,
switchOutCurve:
Curves.easeOutCirc,
child:
clickedForgotPassword
? Column(
key: const ValueKey(
'otp_block'),
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment CrossAxisAlignment.start,
.start,
children: [ children: [
Container( Center(
child: Center( child: Text(
child:
Text(
'OTP', 'OTP',
style: style: TextStyle(
TextStyle( fontSize: Responsive
fontSize: Responsive.isMobile(context) .isMobile(
context)
? 14 ? 14
: 18, : 18,
fontWeight: fontWeight:
FontWeight.w600, FontWeight
.w600,
color: color:
Colors.black, Colors.black,
), ),
), ),
), ),
),
const SizedBox( const SizedBox(
height: height: 10),
10),
Container( Container(
alignment: Alignment.center, alignment:
margin: Responsive.isDesktop(context) Alignment.center,
? EdgeInsets.symmetric( margin: Responsive
horizontal: 150) .isDesktop(
: EdgeInsets.symmetric( context)
horizontal: 0), ? const EdgeInsets
.symmetric(
horizontal:
150)
: const EdgeInsets
.symmetric(
horizontal:
0),
child: Pinput( child: Pinput(
length: 6, length: 6,
// defaultPinTheme: defaultPinTheme,
// focusedPinTheme: focusedPinTheme,
// submittedPinTheme: submittedPinTheme,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter FilteringTextInputFormatter
.digitsOnly, // allows only 09 .digitsOnly,
], ],
keyboardType: TextInputType.number, keyboardType:
TextInputType
.number,
showCursor: true, showCursor: true,
controller: _otpController, controller:
_otpController,
validator: validator:
(value) { (value) {
if (value == null || if (value ==
value.isEmpty) { null ||
value
.isEmpty) {
return 'Please enter OTP'; return 'Please enter OTP';
} }
if (value.length < if (value
.length <
6) { 6) {
return 'OTP must be 6 digits'; return 'OTP must be 6 digits';
} }
// if (otpValueStatus) {
// // example
// return 'Invalid OTP';
// }
return null; return null;
}, },
), ),
), ),
const SizedBox( const SizedBox(
height: height: 10),
10),
Container( Container(
margin: Responsive.isDesktop(context) margin: Responsive
? EdgeInsets.symmetric( .isDesktop(
context)
? const EdgeInsets
.symmetric(
horizontal: horizontal:
150) 150)
: EdgeInsets.symmetric( : const EdgeInsets
.symmetric(
horizontal: horizontal:
0), 0),
alignment: alignment: Alignment
Alignment .centerRight,
.centerRight, // center the text child: InkWell(
child:InkWell( onTap: () {
onTap: (){
setState(() { setState(() {
otpFieldShow = false; otpFieldShow =
false;
}); });
print('ABCDEF');
resendOTP(); resendOTP();
}, },
mouseCursor: SystemMouseCursors.click, mouseCursor:
SystemMouseCursors
.click,
child: Text( child: Text(
'Didnt Receive Code?', 'Didnt Receive Code?',
style: GoogleFonts.poppins( style: GoogleFonts
color: Colors.blue, .poppins(
color: Colors
.blue,
fontSize: 14, fontSize: 14,
), ),
), ),
), ),
), ),
], ],
)
: const SizedBox
.shrink(), // Empty widget when false
), ),
], ],
SizedBox(height: 20), SizedBox(height: 20),

View File

@ -127,13 +127,15 @@ class _AddPolicyScreenState extends State<AddPolicyScreen> {
policyNoController.clear(); policyNoController.clear();
expDateController.clear(); expDateController.clear();
setState(() => isLoading = false);
context.go('/home'); context.go('/home');
} }
} }
} catch (e) { } catch (e) {
print('Submit error: $e'); print('Submit error: $e');
} finally {
if (mounted) {
setState(() => isLoading = false);
}
} }
} }

View File

@ -177,7 +177,8 @@ import '../service/popup_helper.dart';
canPop: false, canPop: false,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
if (didPop) return; if (didPop) return;
context.go('/claims'); // context.go('/claims');
context.pop();
}, },
child: Scaffold( child: Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -209,7 +210,7 @@ import '../service/popup_helper.dart';
flex: 12, flex: 12,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.go('/claims'); context.pop(); // context.go('/claims');
}, },
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:

View File

@ -311,11 +311,18 @@ class _claimsState extends State<claims> {
if (widget.initialTab == 2) { if (widget.initialTab == 2) {
context.go('/home'); context.go('/home');
} }
if (widget.initialTab == 0) {
context.go('/home'); if (Navigator.canPop(context)) {
} else {
context.pop(); context.pop();
} else {
context.go('/home');
} }
// if (widget.initialTab == 0) {
// context.pop();
// } else {
// context.pop();
// }
}, },
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -993,14 +1000,14 @@ class _claimsState extends State<claims> {
var details = { var details = {
"retailDetails": item, "retailDetails": item,
}; };
context.go('/retailClaimForm', extra: details); context.push('/retailClaimForm', extra: details);
} else { } else {
// Normal claim policy click // Normal claim policy click
var details = { var details = {
"claimsDetails": item, "claimsDetails": item,
"fromClaimPage": 0, "fromClaimPage": 0,
}; };
context.go('/planclaimsform', extra: details); context.push('/planclaimsform', extra: details);
} }
}, },
child: MouseRegion( child: MouseRegion(

View File

@ -191,12 +191,56 @@ class _generalExclusionsDeductiblesState
: EdgeInsets.all(10), : EdgeInsets.all(10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 12,
child: InkWell(
onTap: () {
context.go('/home');
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
Icons.chevron_left,
color: Color(0xFF000000),
size: 30,
),
SizedBox(
width: Responsive.isDesktop(context)
? 0
: 5),
Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'General Exclusions & Deductibles', 'General Exclusions & Deductibles',
textAlign: TextAlign.start,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 18, fontWeight: FontWeight.w600), fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
), ),
),
],
),
],
),
),
),
],
),
// Text(
// 'General Exclusions & Deductibles',
// style: GoogleFonts.poppins(
// fontSize: 18, fontWeight: FontWeight.w600),
// ),
const SizedBox(height: 20), const SizedBox(height: 20),
if (type3Content.isNotEmpty) ...[ if (type3Content.isNotEmpty) ...[
Text(type3SectionName ?? '', Text(type3SectionName ?? '',

View File

@ -336,31 +336,31 @@ class _helpState extends State<help> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( // Row(
mainAxisAlignment: MainAxisAlignment.start, // mainAxisAlignment: MainAxisAlignment.start,
children: [ // children: [
Expanded( // Expanded(
flex: 12, // flex: 12,
child: InkWell( // child: InkWell(
onTap: () { // onTap: () {
context.push('/claims'); // context.push('/claims');
// Navigator.pushNamed(context, 'home'); // // Navigator.pushNamed(context, 'home');
}, // },
child: Row( // child: Row(
mainAxisAlignment: MainAxisAlignment.start, // mainAxisAlignment: MainAxisAlignment.start,
children: [ // children: [
Icon( // Icon(
Icons // Icons
.chevron_left, // Replace with your desired icon // .chevron_left, // Replace with your desired icon
color: Color(0xFF000000), // color: Color(0xFF000000),
size: 30, // size: 30,
), // ),
], // ],
), // ),
), // ),
), // ),
], // ],
), // ),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [

View File

@ -1880,10 +1880,44 @@ class _HomeState extends State<Home> {
cards.add(_buildAddCard()); // <-- always add this card cards.add(_buildAddCard()); // <-- always add this card
} }
// NEW: Show empty state when inactive tab has no policies
if (!isActive && cards.isEmpty) {
return _buildEmptyState("No inactive policies available");
}
return _buildCarouselSlider(cards); return _buildCarouselSlider(cards);
} }
Widget _buildEmptyState(String message) {
return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
decoration: BoxDecoration(
color: const Color(0xFFF7F7F7),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFE0E0E0), width: 1.5),
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.folder_off_outlined, size: 48, color: Colors.grey[400]),
const SizedBox(height: 12),
Text(
message,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildCarouselSlider(List<Widget> cards) { Widget _buildCarouselSlider(List<Widget> cards) {
final int totalCards = cards.length; final int totalCards = cards.length;
final bool hasAddCard = isActive; // because you add AddCard only when isActive final bool hasAddCard = isActive; // because you add AddCard only when isActive

View File

@ -132,6 +132,7 @@ class _planclaimsformState extends State<planclaimsform> {
bool isIntimationDateValid = true; bool isIntimationDateValid = true;
bool isAdmitDateValid = true; bool isAdmitDateValid = true;
bool isDischargeDateValid = true; bool isDischargeDateValid = true;
bool showFileError = false;
final session = SessionManager(); final session = SessionManager();
@ -541,55 +542,70 @@ class _planclaimsformState extends State<planclaimsform> {
} }
Future<void> sendFormDataToApi() async { Future<void> sendFormDataToApi() async {
setState(() => isSubmitting = true); // 🔥 start loader final isServiceValid = serviceId != null;
setState(() { final isPolicyValid = policyNumberId != null;
isServiceValid = serviceId != null; final isMemberValid = selectedMemberId != null;
isPolicyValid = policyNumberId != null; final isSubjectValid = subjectController.text.trim().isNotEmpty;
isMemberValid = selectedMemberId != null; final isHospitalNameValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalNameController.text.trim().isNotEmpty;
isSubjectValid = subjectController.text.trim().isNotEmpty; final isHospitalAddressValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalAddressController.text.trim().isNotEmpty;
isHospitalNameValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalNameController.text.trim().isNotEmpty; final isHospitalStateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalStateController.text.trim().isNotEmpty;
isHospitalAddressValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalAddressController.text.trim().isNotEmpty; final isHospitalCityValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalCityController.text.trim().isNotEmpty;
isHospitalStateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalStateController.text.trim().isNotEmpty; final isHospitalPincodeValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPinCodeController.text.trim().isNotEmpty;
isHospitalCityValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalCityController.text.trim().isNotEmpty; final isHospitalPhoneNoValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || (hospitalPhoneNoController.text.trim().length == 10);
isHospitalPincodeValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPinCodeController.text.trim().isNotEmpty; final isAdmitDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || admitDate != null;
isHospitalPhoneNoValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || hospitalPhoneNoController.text.trim().isNotEmpty; final isDischargeDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || dischargeDate != null;
// isAdmitDischargeValid = policyTypeCondition != 1 || (admitDate != null && dischargeDate != null); final isClaimAmountValid = serviceId != 1 || claimAmountController.text.trim().isNotEmpty;
isAdmitDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || admitDate != null; final isAccidentDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? accidentDate != null : true;
isDischargeDateValid = (policyTypeCondition != 1 && policyTypeCondition != 72) || dischargeDate != null; final isIntimationDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? intimationDate != null : true;
isClaimAmountValid = serviceId != 1 || claimAmountController.text.trim().isNotEmpty; final areFilesUploaded = FileUploadService().files.isNotEmpty;
isAccidentDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? accidentDate != null : true;
isIntimationDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? intimationDate != null : true;
});
if (isServiceValid && if (!isServiceValid ||
isPolicyValid && !isPolicyValid ||
isMemberValid && !isMemberValid ||
isSubjectValid && !isSubjectValid ||
isHospitalNameValid && !isHospitalNameValid ||
isHospitalAddressValid && !isHospitalAddressValid ||
isHospitalStateValid && !isHospitalStateValid ||
isHospitalCityValid && !isHospitalCityValid ||
isHospitalPincodeValid && !isHospitalPincodeValid ||
isHospitalPhoneNoValid && !isHospitalPhoneNoValid ||
isAdmitDateValid && !isAdmitDateValid ||
isDischargeDateValid && !isDischargeDateValid ||
isClaimAmountValid && !isClaimAmountValid ||
isAccidentDateValid && !isAccidentDateValid ||
isIntimationDateValid) { !isIntimationDateValid) {
if (FileUploadService().files.isEmpty) {
setState(() { setState(() {
MultiFileUploadWidget.hasFiles = false; this.isServiceValid = isServiceValid;
this.isPolicyValid = isPolicyValid;
this.isMemberValid = isMemberValid;
this.isSubjectValid = isSubjectValid;
this.isHospitalNameValid = isHospitalNameValid;
this.isHospitalAddressValid = isHospitalAddressValid;
this.isHospitalStateValid = isHospitalStateValid;
this.isHospitalCityValid = isHospitalCityValid;
this.isHospitalPincodeValid = isHospitalPincodeValid;
this.isHospitalPhoneNoValid = isHospitalPhoneNoValid;
this.isAdmitDateValid = isAdmitDateValid;
this.isDischargeDateValid = isDischargeDateValid;
this.isClaimAmountValid = isClaimAmountValid;
this.isAccidentDateValid = isAccidentDateValid;
this.isIntimationDateValid = isIntimationDateValid;
showFileError = true; // Also show file error if other fields are invalid
}); });
ToastHelper.showErrorToast(context, 'Please upload at least one document');
return;
}
// Proceed to submit
} else {
ToastHelper.showErrorToast(context, 'Please Fill Required Fields'); ToastHelper.showErrorToast(context, 'Please Fill Required Fields');
return; return;
} }
if (!areFilesUploaded) {
setState(() { setState(() {
showFileError = true;
});
ToastHelper.showErrorToast(context, 'Please upload the document');
return;
}
setState(() {
isSubmitting = true;
isLoading = true; isLoading = true;
}); });
@ -658,7 +674,8 @@ class _planclaimsformState extends State<planclaimsform> {
_token = await TokenService.getPostToken(); _token = await TokenService.getPostToken();
final request = http.MultipartRequest('POST', Uri.parse('${Environment.apiUrl}initiateClaim')); final request = http.MultipartRequest(
'POST', Uri.parse('${Environment.apiUrl}initiateClaim'));
request.headers['Authorization'] = 'Bearer $_token'; request.headers['Authorization'] = 'Bearer $_token';
request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? '')); final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? ''));
@ -674,7 +691,10 @@ class _planclaimsformState extends State<planclaimsform> {
print("📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'"); print("📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'");
if ((uf.label ?? '').trim().isEmpty) { if ((uf.label ?? '').trim().isEmpty) {
ToastHelper.showErrorToast(context, 'Please enter name for all uploaded documents'); ToastHelper.showErrorToast(context, 'Please enter name for all uploaded documents');
setState(() => isLoading = false); setState(() {
isLoading = false;
isSubmitting = false; // reset loader
});
return; return;
} }
} }
@ -715,33 +735,20 @@ class _planclaimsformState extends State<planclaimsform> {
// Convert image PDF // Convert image PDF
final pdf = pw.Document(); final pdf = pw.Document();
final image = pw.MemoryImage(fileBytes); final image = pw.MemoryImage(fileBytes);
pdf.addPage(pw.Page(
pdf.addPage( build: (pw.Context context) =>
pw.Page( pw.Center(child: pw.Image(image, fit: pw.BoxFit.contain)),
build: (pw.Context context) => pw.Center( ));
child: pw.Image(image, fit: pw.BoxFit.contain), fileBytes = await pdf.save();
),
),
);
fileBytes = await pdf.save(); // converted PDF bytes
// replace file name with .pdf extension
final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf'); final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
print('📄 Converted image ${pf.name} → PDF ($pdfFileName)'); print('📄 Converted image ${pf.name} → PDF ($pdfFileName)');
request.files.add(http.MultipartFile.fromBytes( request.files.add(http.MultipartFile.fromBytes(
'claim_docs[]', 'claim_docs[]', fileBytes, filename: pdfFileName));
fileBytes,
filename: pdfFileName,
));
} else { } else {
// Already a PDF // Already a PDF
request.files.add(http.MultipartFile.fromBytes( request.files.add(http.MultipartFile.fromBytes(
'claim_docs[]', 'claim_docs[]', fileBytes, filename: pf.name));
fileBytes,
filename: pf.name,
));
} }
} }
} }
@ -762,31 +769,25 @@ class _planclaimsformState extends State<planclaimsform> {
final responseBody = await response.stream.bytesToString(); final responseBody = await response.stream.bytesToString();
final decoded = jsonDecode(responseBody); final decoded = jsonDecode(responseBody);
if (decoded['status'] == true) { if (decoded['status'] == true) {
ToastHelper.showSuccessToast(context, decoded['message']); ToastHelper.showSuccessToast(context, decoded['message']);
serviceId = null; serviceId = null;
departmentList.clear(); departmentList.clear();
setState(() { setState(() => isLoading = false);
isLoading = false;
});
context.go('/claims', extra: 2); context.go('/claims', extra: 2);
print('Form data submitted successfully'); print('Form data submitted successfully');
fileService.clearAll(); fileService.clearAll();
} else { } else {
setState(() { setState(() => isLoading = false);
isLoading = false;
});
ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}"); ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}");
} }
} catch (e) { } catch (e) {
setState(() { setState(() => isLoading = false);
isLoading = false;
});
print('Error submitting form data: $e'); print('Error submitting form data: $e');
} finally { } finally {
setState(() => isSubmitting = false); // 🔥 stop loader setState(() => isSubmitting = false); // 🔥 always stop loader
} }
} }
@ -878,8 +879,7 @@ class _planclaimsformState extends State<planclaimsform> {
canPop: false, canPop: false,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
if (didPop) return; if (didPop) return;
final route = fromClaimsPage == 0 ? 'claims' : 'help'; context.pop();
context.go('/$route');
}, },
child: Scaffold( child: Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -956,8 +956,7 @@ class _planclaimsformState extends State<planclaimsform> {
flex: 1, flex: 1,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
final route = fromClaimsPage == 0 ? 'claims' : 'help'; context.pop();
context.go('/$route');
}, },
child: Icon( child: Icon(
Icons Icons
@ -1025,7 +1024,7 @@ class _planclaimsformState extends State<planclaimsform> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildDropdownField( buildDropdownField(
'Service', 'Service',isRequired: true,
(value) { (value) {
setState(() { setState(() {
serviceId = value; serviceId = value;
@ -1084,7 +1083,7 @@ class _planclaimsformState extends State<planclaimsform> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildDropdownField( buildDropdownField(
'Select Policy', 'Select Policy',isRequired: true,
(value) { (value) {
setState(() { setState(() {
policyNumberId = value; policyNumberId = value;
@ -1149,7 +1148,7 @@ class _planclaimsformState extends State<planclaimsform> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildDropdownField( buildDropdownField(
'Member Name', 'Member Name',isRequired: true,
(value) { (value) {
setState(() { setState(() {
selectedMemberId = value; selectedMemberId = value;
@ -1212,37 +1211,44 @@ SizedBox(height: 15),
// accidentDetailsController), // accidentDetailsController),
SizedBox(height: 15), SizedBox(height: 15),
if (policyTypeCondition == 1 || policyTypeCondition == 72) ...[ if (policyTypeCondition == 1 || policyTypeCondition == 72) ...[
buildTextField('Hospital Name', hospitalNameController), buildTextField('Hospital Name', hospitalNameController, isRequired: true),
if (!isHospitalNameValid) if (!isHospitalNameValid)
Text('Please enter the Hospital Name', style: TextStyle(color: Colors.red)), Text('Please enter the Hospital Name', style: TextStyle(color: Colors.red)),
SizedBox(height: 15), SizedBox(height: 15),
buildTextAreaField('Hospital Address',hospitalAddressController), buildTextAreaField('Hospital Address', hospitalAddressController, isRequired: true),
if (!isHospitalAddressValid) if (!isHospitalAddressValid)
Text('Please enter the Hospital Address', style: TextStyle(color: Colors.red)), Text('Please enter the Hospital Address', style: TextStyle(color: Colors.red)),
SizedBox(height: 15), SizedBox(height: 15),
buildTextField('Hospital City', hospitalCityController), buildTextField('Hospital City', hospitalCityController,isRequired: true,inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z ]'))]),
if (!isHospitalCityValid) if (!isHospitalCityValid)
Text('Please enter the Hospital City', style: TextStyle(color: Colors.red)), Text('Please enter the Hospital City', style: TextStyle(color: Colors.red)),
SizedBox(height: 15), SizedBox(height: 15),
buildTextField('Hospital State', hospitalStateController), buildTextField('Hospital State', hospitalStateController,isRequired: true,inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z ]'))]),
if (!isHospitalStateValid) if (!isHospitalStateValid)
Text('Please enter the Hospital State', style: TextStyle(color: Colors.red)), Text('Please enter the Hospital State', style: TextStyle(color: Colors.red)),
SizedBox(height: 15), SizedBox(height: 15),
buildTextField('Hospital Pincode', hospitalPinCodeController,keyboardType: TextInputType.number, buildTextField('Hospital Pincode', hospitalPinCodeController,isRequired: true,keyboardType: TextInputType.number,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6), // optional LengthLimitingTextInputFormatter(6),
]), ]),
if (!isHospitalPincodeValid) if (!isHospitalPincodeValid)
Text('Please enter the Hospital Pincode', style: TextStyle(color: Colors.red)), Text('Please enter the Hospital Pincode', style: TextStyle(color: Colors.red)),
SizedBox(height: 15), SizedBox(height: 15),
buildTextField('Hospital Phone No', hospitalPhoneNoController,keyboardType: TextInputType.number, buildTextField('Hospital Phone No', hospitalPhoneNoController,
isRequired: true,
keyboardType: TextInputType.number,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10), // optional LengthLimitingTextInputFormatter(10),
]), ],minLength: 10,),
if (!isHospitalPhoneNoValid) if (!isHospitalPhoneNoValid)
Text('Please enter the Hospital Phone No', style: TextStyle(color: Colors.red)), Text(
hospitalPhoneNoController.text.trim().isEmpty
? 'Please enter the Hospital Phone No'
: 'Hospital Phone No must be 10 digits', // specific message
style: TextStyle(color: Colors.red),
),
], ],
SizedBox(height: 15), SizedBox(height: 15),
if (serviceId == 2 || if (serviceId == 2 ||
@ -1265,6 +1271,7 @@ SizedBox(height: 15),
SizedBox(height: 15), SizedBox(height: 15),
buildDatePickerField( buildDatePickerField(
label: 'Accident Date', label: 'Accident Date',
isRequired: true,
selectedDate: accidentDate, selectedDate: accidentDate,
allowFuture: false, allowFuture: false,
minDate: parsedPolicyStartDate, minDate: parsedPolicyStartDate,
@ -1285,6 +1292,7 @@ SizedBox(height: 15),
'Date of Death', 'Date of Death',
selectedDate: selectedDate:
deathDate, deathDate,
isRequired: true,
allowFuture: false, allowFuture: false,
minDate: accidentDate ?? parsedPolicyStartDate, minDate: accidentDate ?? parsedPolicyStartDate,
maxDate: parsedPolicyEndDate, maxDate: parsedPolicyEndDate,
@ -1299,6 +1307,7 @@ SizedBox(height: 15),
SizedBox(height: 15), SizedBox(height: 15),
buildDatePickerField( buildDatePickerField(
label: 'Date of Intimation', label: 'Date of Intimation',
isRequired: true,
selectedDate: intimationDate, selectedDate: intimationDate,
allowFuture: false, allowFuture: false,
minDate: accidentDate ?? deathDate ?? parsedPolicyStartDate, minDate: accidentDate ?? deathDate ?? parsedPolicyStartDate,
@ -1321,6 +1330,7 @@ SizedBox(height: 15),
// 🟡 Admit Date no future allowed // 🟡 Admit Date no future allowed
buildDatePickerField( buildDatePickerField(
label: 'Admit Date', label: 'Admit Date',
isRequired: true,
selectedDate: admitDate, selectedDate: admitDate,
allowFuture: false, allowFuture: false,
onDateSelected: (selectedDate) { onDateSelected: (selectedDate) {
@ -1337,6 +1347,7 @@ SizedBox(height: 15),
// 🟢 Discharge Date must be after Admit Date // 🟢 Discharge Date must be after Admit Date
buildDatePickerField( buildDatePickerField(
label: 'Discharge Date', label: 'Discharge Date',
isRequired: true,
selectedDate: dischargeDate, selectedDate: dischargeDate,
allowFuture: true, allowFuture: true,
minDate: admitDate != null minDate: admitDate != null
@ -1356,7 +1367,7 @@ SizedBox(height: 15),
SizedBox(height: 15), SizedBox(height: 15),
if (serviceId == 1) ...[ if (serviceId == 1) ...[
buildTextField('Claims Amount', claimAmountController, keyboardType: TextInputType.number, buildTextField('Claims Amount', claimAmountController, isRequired: true, keyboardType: TextInputType.number,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
]), ]),
@ -1371,6 +1382,7 @@ SizedBox(height: 15),
buildTextField( buildTextField(
'Sum Insured', 'Sum Insured',
sumInsuredController, sumInsuredController,
isRequired: true,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
@ -1396,7 +1408,7 @@ SizedBox(height: 15),
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
const MultiFileUploadWidget(), MultiFileUploadWidget(isRequired: true,showError: showFileError),
], ],
), ),
), ),
@ -1532,32 +1544,38 @@ SizedBox(height: 15),
); );
} }
Widget buildTextField( Widget buildTextField(
String label, String label,
TextEditingController controller, { TextEditingController controller, {
bool isRequired = false, // 👈 ADD THIS
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
List<TextInputFormatter>? inputFormatters, List<TextInputFormatter>? inputFormatters,
int? minLength,
}) { }) {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
keyboardType: keyboardType, keyboardType: keyboardType,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
decoration: InputDecoration(labelText: label), decoration: InputDecoration(
labelText: isRequired ? '$label *' : label, // 👈 ADD THIS
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter the $label'; return 'Please enter the $label';
} }
if (minLength != null && value.trim().length < minLength) { // 👈 ADD this
return '$label must be at least $minLength digits';
}
return null; return null;
}, },
); );
} }
Widget buildTextAreaField(String label, TextEditingController controller) { Widget buildTextAreaField(String label, TextEditingController controller ,{ bool isRequired = false}) {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
decoration: InputDecoration(labelText: label), decoration: InputDecoration(labelText: isRequired ? '$label *' : label),
maxLines: 5, maxLines: 5,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@ -1573,6 +1591,7 @@ SizedBox(height: 15),
required DateTime? selectedDate, required DateTime? selectedDate,
required bool allowFuture, required bool allowFuture,
required ValueChanged<DateTime?> onDateSelected, required ValueChanged<DateTime?> onDateSelected,
bool isRequired = false,
DateTime? minDate, DateTime? minDate,
DateTime? maxDate, DateTime? maxDate,
}) { }) {
@ -1623,7 +1642,7 @@ SizedBox(height: 15),
}, },
child: InputDecorator( child: InputDecorator(
decoration: InputDecoration( decoration: InputDecoration(
labelText: label, labelText: isRequired ? '$label *' : label,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
child: Text( child: Text(
@ -1643,11 +1662,12 @@ SizedBox(height: 15),
bool readOnly, bool readOnly,
List<Map<String, dynamic>> itemsList, List<Map<String, dynamic>> itemsList,
String displayField, String displayField,
int? selectedValue // Added selectedValue parameter int? selectedValue, // Added selectedValue parameter
{bool isRequired = false,}
) { ) {
return DropdownButtonFormField<int>( return DropdownButtonFormField<int>(
value: selectedValue, value: selectedValue,
decoration: InputDecoration(labelText: label), decoration: InputDecoration(labelText: isRequired ? '$label *' : label,),
items: itemsList.map<DropdownMenuItem<int>>((item) { items: itemsList.map<DropdownMenuItem<int>>((item) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: item['id'], // Ensure 'id' is correctly referenced value: item['id'], // Ensure 'id' is correctly referenced

View File

@ -322,12 +322,8 @@ class ApiService {
} }
Future<void> _clearLocalStorageAndRedirect() async { Future<void> _clearLocalStorageAndRedirect() async {
final prefs = await SharedPreferences.getInstance(); if (!context.mounted) return;
await prefs.clear(); await TokenService().logout(context);
// Assuming you have access to the context
ToastHelper.showErrorToast(context, 'Session Out');
context.go('/login');
// Navigator.pushNamed(context, 'login');
} }
Future<Map<String, dynamic>> getBotDetails( Future<Map<String, dynamic>> getBotDetails(
@ -407,6 +403,7 @@ class ApiService {
await _clearLocalStorageAndRedirect(); await _clearLocalStorageAndRedirect();
return {}; return {};
} else if (response.statusCode == 429) { } else if (response.statusCode == 429) {
if (!context.mounted) return {};
final body = jsonDecode(response.body); final body = jsonDecode(response.body);
final message = body['message']; final message = body['message'];
ToastHelper.showWarningToast(context, message); ToastHelper.showWarningToast(context, message);

View File

@ -4,8 +4,15 @@ import 'file_upload_service.dart';
class MultiFileUploadWidget extends StatefulWidget { class MultiFileUploadWidget extends StatefulWidget {
final bool forceMobile; final bool forceMobile;
final bool isRequired;
final bool showError; // ADD THIS
const MultiFileUploadWidget({super.key, this.forceMobile = false}); const MultiFileUploadWidget({
super.key,
this.forceMobile = false,
this.isRequired = false,
this.showError = false, // ADD THIS
});
@override @override
State<MultiFileUploadWidget> createState() => _MultiFileUploadWidgetState(); State<MultiFileUploadWidget> createState() => _MultiFileUploadWidgetState();
@ -18,27 +25,12 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
String? errorMessage; String? errorMessage;
void _pickFiles() async { void _pickFiles() async {
final error = await fileService.pickFiles(maxFileSizeInMB: 10); // 5 MB limit final error = await fileService.pickFiles(maxFileSizeInMB: 10);
if (error != null) { if (error != null) {
if (mounted) { if (mounted) {
setState(() { setState(() {
errorMessage = error; errorMessage = error;
}); });
// also show alert dialog for big error messages
// showDialog(
// context: context,
// builder: (ctx) => AlertDialog(
// title: const Text("File Upload Error"),
// content: Text(error),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(ctx),
// child: const Text("OK"),
// ),
// ],
// ),
// );
} }
} else { } else {
setState(() { setState(() {
@ -55,6 +47,31 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
}); });
} }
// Build the button label with RichText to show * in red
Widget _buildButtonLabel() {
if (!widget.isRequired) {
return const Text(
"Upload Documents",
style: TextStyle(fontSize: 14, color: Colors.black),
overflow: TextOverflow.ellipsis,
);
}
return RichText(
text: const TextSpan(
children: [
TextSpan(
text: "Upload Documents ",
style: TextStyle(fontSize: 14, color: Colors.black),
),
TextSpan(
text: "*", // red * like other fields
style: TextStyle(fontSize: 14, color: Colors.red),
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final files = fileService.files; final files = fileService.files;
@ -67,21 +84,14 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
onPressed: _pickFiles, onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined, icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFFE26728), size: 24), color: Color(0xFFE26728), size: 24),
label: const Text( label: _buildButtonLabel(), // use rich text label
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFFE26728)), side: const BorderSide(color: Color(0xFFE26728)),
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
const Text( const Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)", "Supports only PDF, PNG, JPG, JPEG, HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey), style: TextStyle(fontSize: 12, color: Colors.grey),
), ),
] else ...[ ] else ...[
@ -91,14 +101,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
onPressed: _pickFiles, onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined, icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFFE26728), size: 24), color: Color(0xFFE26728), size: 24),
label: const Text( label: _buildButtonLabel(), // use rich text label
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFFE26728)), side: const BorderSide(color: Color(0xFFE26728)),
), ),
@ -106,7 +109,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded( const Expanded(
child: Text( child: Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)", "Supports only PDF, PNG, JPG, JPEG, HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey), style: TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -115,10 +118,11 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
), ),
], ],
if (fileService.files.isEmpty && errorMessage == null) ...[ // Show error only when required AND no files
if (widget.isRequired && widget.showError && files.isEmpty && errorMessage == null) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( const Text(
"Required", 'Please upload the document',
style: TextStyle(color: Colors.red, fontSize: 12), style: TextStyle(color: Colors.red, fontSize: 12),
), ),
], ],
@ -134,7 +138,7 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
const SizedBox(height: 8), const SizedBox(height: 8),
...fileService.files.asMap().entries.map((entry) { ...fileService.files.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final uploaded = entry.value; // UploadedFile final uploaded = entry.value;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -142,7 +146,8 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
ListTile( ListTile(
dense: true, dense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text(uploaded.file.name, style: const TextStyle(fontSize: 14)), title: Text(uploaded.file.name,
style: const TextStyle(fontSize: 14)),
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.close, color: Colors.red), icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => _removeFile(index), onPressed: () => _removeFile(index),

View File

@ -66,6 +66,14 @@ class _setPasswordState extends State<setPassword> {
final confirmPassword = confirmPasswordController.text.trim(); final confirmPassword = confirmPasswordController.text.trim();
try { try {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
final strongPasswordRegex =
RegExp(r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&]).{8,}$');
if (!strongPasswordRegex.hasMatch(newPassword)) {
ToastHelper.showErrorToast(
context,
'Enter a valid password (8+ chars with letter, number, special char)');
return;
}
if (confirmPassword != newPassword) { if (confirmPassword != newPassword) {
ToastHelper.showErrorToast(context, 'Passwords do not match'); ToastHelper.showErrorToast(context, 'Passwords do not match');
return; return;
@ -182,15 +190,20 @@ class _setPasswordState extends State<setPassword> {
} }
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
// Close the app on mobile back button press // 🖥 Desktop / Web always go back to login
exit(0); // This will exit the app if (kIsWeb || Responsive.isDesktop(context)) {
return false; // Return false to prevent any other actions context.go('/login');
return false;
}
// 📱 Mobile keep existing behavior (exit app)
exit(0);
return false;
}, },
child: Scaffold( child: Scaffold(
body: SingleChildScrollView( body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container( child: Container(
height: _size.height, constraints: BoxConstraints(minHeight: _size.height),
color: Colors.white, color: Colors.white,
child: Stack( child: Stack(
children: [ children: [
@ -277,7 +290,7 @@ class _setPasswordState extends State<setPassword> {
// ), // ),
Container( Container(
margin: marginInsets, margin: marginInsets,
alignment: Alignment.bottomCenter, alignment: Alignment.center,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
@ -322,8 +335,7 @@ class _setPasswordState extends State<setPassword> {
Expanded( Expanded(
flex: 12, flex: 12,
child: Align( child: Align(
alignment: Alignment alignment: Alignment.center,
.topLeft, // Always top-left
child: _size.width <= 1100 child: _size.width <= 1100
? Image.asset( ? Image.asset(
'assets/nhance_app_logo.png', 'assets/nhance_app_logo.png',
@ -347,10 +359,9 @@ class _setPasswordState extends State<setPassword> {
), ),
SizedBox( SizedBox(
height: Responsive.isDesktop(context) height: Responsive.isDesktop(context)
? _size.height * 0.1 ? null
: 10, : _size.height * 0.2,
), ),
SizedBox(height: 10),
Container( Container(
margin: Responsive.isDesktop(context) margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
@ -433,6 +444,25 @@ class _setPasswordState extends State<setPassword> {
}, },
), ),
), ),
validator: (value) {
final password = value ?? '';
if (password.isEmpty) {
return 'Please enter your new password';
}
if (password.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(r'[A-Za-z]').hasMatch(password)) {
return 'Password must include at least 1 letter';
}
if (!RegExp(r'\d').hasMatch(password)) {
return 'Password must include at least 1 number';
}
if (!RegExp(r'[@$!%*#?&]').hasMatch(password)) {
return 'Password must include at least 1 special character';
}
return null;
},
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -573,10 +603,10 @@ class _setPasswordState extends State<setPassword> {
), ),
), ),
), ),
// _size.height * 0.3
SizedBox( SizedBox(
height: Responsive.isDesktop(context) height: Responsive.isDesktop(context)
? _size.height * 0.3 ? null
: _size.height * 0.2, : _size.height * 0.2,
), ),
// SizedBox( // SizedBox(