changes done
This commit is contained in:
parent
75e5baf0fd
commit
1e97c41545
@ -32,6 +32,7 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
|
||||
List<Map<String, dynamic>> sideMenuItems = [];
|
||||
List<int> postModules = [];
|
||||
List<int> enrollmentModules = [];
|
||||
List<String> claimsSubMenu = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -59,8 +60,26 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
|
||||
? List<int>.from(jsonDecode(postRaw))
|
||||
: [];
|
||||
|
||||
final claimsSubMenuRaw = await tokenService.readValue('claims_sub_menu');
|
||||
if (claimsSubMenuRaw != null && claimsSubMenuRaw.isNotEmpty) {
|
||||
claimsSubMenu = List<dynamic>.from(jsonDecode(claimsSubMenuRaw))
|
||||
.map((e) => e.toString())
|
||||
.toList();
|
||||
} else {
|
||||
// Fallback: decoded JWT / selected branch may still have the list.
|
||||
final decoded = tokenService.getDecodedToken();
|
||||
final branch = tokenService.getSelectedBranch();
|
||||
final fromDecoded = decoded?['claims_sub_menu'];
|
||||
final fromBranch = branch?['claims_sub_menu'];
|
||||
final source = fromBranch is List
|
||||
? fromBranch
|
||||
: (fromDecoded is List ? fromDecoded : const []);
|
||||
claimsSubMenu = source.map((e) => e.toString()).toList();
|
||||
}
|
||||
|
||||
logDebug('enrollmentModules $enrollmentModules');
|
||||
logDebug('postModules $postModules');
|
||||
logDebug('claimsSubMenu $claimsSubMenu');
|
||||
|
||||
final List<Map<String, dynamic>> items = [];
|
||||
|
||||
@ -82,8 +101,9 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
|
||||
});
|
||||
}
|
||||
|
||||
// CLAIMS
|
||||
if (postModules.contains(4)) {
|
||||
// CLAIMS — only when module 4 and at least one sub-menu is allowed
|
||||
if (postModules.contains(4) &&
|
||||
(claimsSubMenu.contains('EB') || claimsSubMenu.contains('Non-EB'))) {
|
||||
items.add({
|
||||
'route': 'ClaimsPolicies',
|
||||
'label': 'Claims',
|
||||
@ -173,8 +193,10 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildClaimsMenuItem('EB', 'ClaimsPolicies'),
|
||||
_buildClaimsMenuItem('Non-EB', 'nonEBClaimsList'),
|
||||
if (claimsSubMenu.contains('EB'))
|
||||
_buildClaimsMenuItem('EB', 'ClaimsPolicies'),
|
||||
if (claimsSubMenu.contains('Non-EB'))
|
||||
_buildClaimsMenuItem('Non-EB', 'nonEBClaimsList'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
278
lib/main.dart
278
lib/main.dart
@ -181,6 +181,52 @@ Future<void> startApp() async {
|
||||
runApp(MyApp(initialToken: token));
|
||||
}
|
||||
|
||||
/// Routes that require an authenticated HR session.
|
||||
/// Public auth-flow routes (hrLogin, mailVerify, branchSelection) stay excluded.
|
||||
const Set<String> _authProtectedRoutes = {
|
||||
'hrHome',
|
||||
'hrDashboard',
|
||||
'policies',
|
||||
'claimsOverviewDashboard',
|
||||
'ClaimsPolicies',
|
||||
'nonEBClaimsList',
|
||||
'CdPoliciesList',
|
||||
'cdTransactionDetails',
|
||||
'hrPolicyDetails',
|
||||
'oldPolicy',
|
||||
'preFileUpload',
|
||||
'postFileUpload',
|
||||
'excelErrorScreen',
|
||||
'empDetails',
|
||||
'addOnsDetails',
|
||||
'empReviewDetails',
|
||||
};
|
||||
|
||||
String _normalizeRouteName(String routeName) {
|
||||
var name = routeName.trim();
|
||||
if (name.startsWith('#')) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
if (name.startsWith('/')) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
// Flutter web may include query/hash fragments; keep only path segment.
|
||||
final hashIndex = name.indexOf('#');
|
||||
if (hashIndex != -1) {
|
||||
name = name.substring(hashIndex + 1);
|
||||
}
|
||||
final queryIndex = name.indexOf('?');
|
||||
if (queryIndex != -1) {
|
||||
name = name.substring(0, queryIndex);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
bool _hasValidSession() {
|
||||
final token = TokenStorageService().getCurrentToken();
|
||||
return token != null && token.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
final String? initialToken;
|
||||
|
||||
@ -188,12 +234,33 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fallbackRoute =
|
||||
(initialToken == null || initialToken!.isEmpty) ? 'hrLogin' : 'hrHome';
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Nhance HR',
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: (initialToken == null || initialToken!.isEmpty)
|
||||
? 'hrLogin'
|
||||
: 'hrHome',
|
||||
initialRoute: fallbackRoute,
|
||||
onGenerateInitialRoutes: (String initialRouteName) {
|
||||
final requested = _normalizeRouteName(initialRouteName);
|
||||
final resolved = (requested.isEmpty || requested == '/')
|
||||
? fallbackRoute
|
||||
: requested;
|
||||
|
||||
// Deep-link guard: unauthenticated access to protected URLs → HR login
|
||||
final targetRoute =
|
||||
(!_hasValidSession() && _authProtectedRoutes.contains(resolved))
|
||||
? 'hrLogin'
|
||||
: resolved;
|
||||
|
||||
final builder = appRoutes[targetRoute] ?? appRoutes['hrLogin']!;
|
||||
return [
|
||||
MaterialPageRoute(
|
||||
builder: builder,
|
||||
settings: RouteSettings(name: targetRoute),
|
||||
),
|
||||
];
|
||||
},
|
||||
theme: ThemeData(
|
||||
primaryColor: const Color(0xFF00999E),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
@ -225,110 +292,159 @@ final Map<String, WidgetBuilder> appRoutes = {
|
||||
// resendToken: null,
|
||||
// onResendCode: (String, int) {},
|
||||
// ),
|
||||
'hrHome': (context) => MyHrHome(),
|
||||
'preFileUpload': (context) => const preFileUpload(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
'hrHome': (context) => AuthWrapper(
|
||||
builder: (_) => MyHrHome(),
|
||||
),
|
||||
'postFileUpload': (context) => const postFileUpload(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
'preFileUpload': (context) => AuthWrapper(
|
||||
builder: (_) => const preFileUpload(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
),
|
||||
),
|
||||
'excelErrorScreen': (context) => const excelErrorScreen(
|
||||
ClientId: '',
|
||||
policy_no: '',
|
||||
action: '',
|
||||
created_at: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
id: ''),
|
||||
'empDetails': (context) => empDetails(),
|
||||
'addOnsDetails': (context) => addOnsDetails(),
|
||||
'empReviewDetails': (context) => empReviewDetails(),
|
||||
'hrDashboard': (context) => policies(),
|
||||
'claimsOverviewDashboard': (context) => const ClaimsOverviewDashboard(),
|
||||
'hrPolicyDetails': (context) => hrPolicyDetails(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
is_ecard_bulk_download_for_employee: 0,
|
||||
'postFileUpload': (context) => AuthWrapper(
|
||||
builder: (_) => const postFileUpload(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
),
|
||||
),
|
||||
'oldPolicy': (context) => oldPolicy(),
|
||||
'excelErrorScreen': (context) => AuthWrapper(
|
||||
builder: (_) => const excelErrorScreen(
|
||||
ClientId: '',
|
||||
policy_no: '',
|
||||
action: '',
|
||||
created_at: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
id: ''),
|
||||
),
|
||||
'empDetails': (context) => AuthWrapper(
|
||||
builder: (_) => empDetails(),
|
||||
),
|
||||
'addOnsDetails': (context) => AuthWrapper(
|
||||
builder: (_) => addOnsDetails(),
|
||||
),
|
||||
'empReviewDetails': (context) => AuthWrapper(
|
||||
builder: (_) => empReviewDetails(),
|
||||
),
|
||||
'hrDashboard': (context) => AuthWrapper(
|
||||
builder: (_) => const policies(),
|
||||
),
|
||||
'claimsOverviewDashboard': (context) => AuthWrapper(
|
||||
builder: (_) => const ClaimsOverviewDashboard(),
|
||||
),
|
||||
'hrPolicyDetails': (context) => AuthWrapper(
|
||||
builder: (_) => hrPolicyDetails(
|
||||
ClientId: '',
|
||||
policyTypeId: '',
|
||||
ClientPoliyId: '',
|
||||
clientBranchId: '',
|
||||
Token: '',
|
||||
TokenType: '',
|
||||
cardType: '',
|
||||
cardPolicyNo: '',
|
||||
cardInsurer_name: '',
|
||||
cardPolicy_name: '',
|
||||
cardPolicy_ExpDate: '',
|
||||
total_premium: '',
|
||||
is_ecard_bulk_download_for_employee: 0,
|
||||
),
|
||||
),
|
||||
'oldPolicy': (context) => AuthWrapper(
|
||||
builder: (_) => oldPolicy(),
|
||||
),
|
||||
// Branch selection happens after login but before a branch token is set.
|
||||
'branchSelection': (context) => BranchSelectionPage(),
|
||||
'policies': (context) => policies(),
|
||||
'CdPoliciesList': (context) => CdPoliciesList(),
|
||||
'ClaimsPolicies': (context) => ClaimsPolicies(
|
||||
empCode: '',
|
||||
'policies': (context) => AuthWrapper(
|
||||
builder: (_) => const policies(),
|
||||
),
|
||||
'nonEBClaimsList': (context) => const NonEBClaimsList(empCode: '',),
|
||||
'cdTransactionDetails': (context) => cdTransactionDetails(
|
||||
insurerName: '',
|
||||
cdMasterAccountNo: '',
|
||||
insurerId: '',
|
||||
cd_ac_pk: '',
|
||||
empClientId: '',
|
||||
'CdPoliciesList': (context) => AuthWrapper(
|
||||
builder: (_) => CdPoliciesList(),
|
||||
),
|
||||
'ClaimsPolicies': (context) => AuthWrapper(
|
||||
builder: (_) => const ClaimsPolicies(
|
||||
empCode: '',
|
||||
),
|
||||
),
|
||||
'nonEBClaimsList': (context) => AuthWrapper(
|
||||
builder: (_) => const NonEBClaimsList(
|
||||
empCode: '',
|
||||
),
|
||||
),
|
||||
'cdTransactionDetails': (context) => AuthWrapper(
|
||||
builder: (_) => cdTransactionDetails(
|
||||
insurerName: '',
|
||||
cdMasterAccountNo: '',
|
||||
insurerId: '',
|
||||
cd_ac_pk: '',
|
||||
empClientId: '',
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
/// 🔐 Global Auth Wrapper (Protects All Pages)
|
||||
/// 🔐 Auth Wrapper — blocks protected pages until a valid session exists.
|
||||
/// Uses a builder so protected widgets are not constructed without a token.
|
||||
class AuthWrapper extends StatefulWidget {
|
||||
final Widget child;
|
||||
final WidgetBuilder builder;
|
||||
|
||||
const AuthWrapper({super.key, required this.child});
|
||||
const AuthWrapper({super.key, required this.builder});
|
||||
|
||||
@override
|
||||
State<AuthWrapper> createState() => _AuthWrapperState();
|
||||
}
|
||||
|
||||
class _AuthWrapperState extends State<AuthWrapper> {
|
||||
late final bool _isAuthenticated;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkLogin();
|
||||
}
|
||||
_isAuthenticated = _hasValidSession();
|
||||
|
||||
void _checkLogin() {
|
||||
final token = TokenStorageService().getCurrentToken();
|
||||
|
||||
if (token == null || token.isEmpty) {
|
||||
if (!_isAuthenticated) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.pushNamedAndRemoveUntil(context, 'hrLogin', (route) => false);
|
||||
if (!mounted) return;
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
if (!_isAuthenticated) {
|
||||
// Do not render protected UI while redirecting to login.
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF00999E),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return widget.builder(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import 'package:universal_html/html.dart' as html;
|
||||
import '../service/secure_pop_scope.dart';
|
||||
import 'claimshistory.dart';
|
||||
import '../customAppBar/base_layout.dart';
|
||||
import '../customAppBar/toastHelper.dart';
|
||||
import '../service/api_service.dart';
|
||||
import '../service/token_storage_service.dart';
|
||||
import 'RaiseClaimForm.dart';
|
||||
@ -74,26 +75,14 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
|
||||
int? appliedClaimStatus;
|
||||
|
||||
Color getStatusColor(String status) {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'claim received':
|
||||
return const Color(0xFF4A90E2); // blue
|
||||
case 'under process':
|
||||
return const Color(0xFFF5A623); // orange
|
||||
case 'information required':
|
||||
return const Color(0xFFFF8C42); // amber
|
||||
case 'approved':
|
||||
return const Color(0xFF2ECC71); // green
|
||||
case 'settled':
|
||||
return const Color(0xFF1ABC9C); // teal-green
|
||||
case 'rejected':
|
||||
return const Color(0xFFE74C3C); // red
|
||||
case 'denial review awaite':
|
||||
return const Color(0xFF9B59B6); // purple
|
||||
case 'closed':
|
||||
return const Color(0xFFB0BEC5); // purple
|
||||
default:
|
||||
return const Color(0xFFB0BEC5); // grey fallback
|
||||
final normalized = status.toLowerCase().trim();
|
||||
if (normalized == 'rejected' || normalized.contains('rejected')) {
|
||||
return const Color(0xFFE26728); // Export button orange
|
||||
}
|
||||
if (normalized == 'settled' || normalized.contains('settled')) {
|
||||
return const Color(0xFF2ECC71); // Green
|
||||
}
|
||||
return const Color(0xFF00A6A6); // List header color
|
||||
}
|
||||
|
||||
Map<String, dynamic> claim_Detials() {
|
||||
@ -148,7 +137,22 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
|
||||
// }
|
||||
|
||||
// getApiData();
|
||||
_loadIds();
|
||||
_checkToken();
|
||||
}
|
||||
|
||||
Future<void> _checkToken() async {
|
||||
final token = await tokenService.getCurrentToken();
|
||||
if (token == null || token.trim().isEmpty) {
|
||||
if (!mounted) return;
|
||||
ToastHelper.showErrorToast(context, 'Session Out');
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _loadIds();
|
||||
}
|
||||
|
||||
Future<void> _loadIds() async {
|
||||
|
||||
@ -41,6 +41,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
bool _isLoading = true;
|
||||
bool _isRefreshing = false;
|
||||
bool _isExportingPdf = false;
|
||||
bool _sessionChecked = false;
|
||||
int _replayToken = 0;
|
||||
int _currentTab = 0;
|
||||
|
||||
@ -89,7 +90,25 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
);
|
||||
_headerFadeController.forward();
|
||||
|
||||
_loadDashboard();
|
||||
_checkToken();
|
||||
}
|
||||
|
||||
Future<void> _checkToken() async {
|
||||
final token = await _tokenService.getCurrentToken();
|
||||
if (token == null || token.trim().isEmpty) {
|
||||
if (!mounted) return;
|
||||
ToastHelper.showErrorToast(context, 'Session Out');
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() => _sessionChecked = true);
|
||||
await _loadDashboard();
|
||||
}
|
||||
|
||||
void _onTabChanged() {
|
||||
@ -499,6 +518,16 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_sessionChecked) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF00999E),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return BaseLayout(
|
||||
child: SecurePopScope(
|
||||
child: Container(
|
||||
|
||||
@ -62,6 +62,8 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
bool isSubmitting = false;
|
||||
|
||||
bool isActionFreeze = false;
|
||||
bool useStatusKeyedTicketData = false;
|
||||
bool useListTicketHistory = false;
|
||||
|
||||
// IR Docs state
|
||||
bool showIRDocs = false;
|
||||
@ -275,22 +277,28 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
if (response['status'] == 'success') {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
final claimsDocs = response['data']['claim_files'];
|
||||
claimFiles = List<Map<String, dynamic>>.from(claimsDocs);
|
||||
final data = response['data'] is Map
|
||||
? Map<String, dynamic>.from(response['data'])
|
||||
: <String, dynamic>{};
|
||||
|
||||
getClaimsHistoryList = [
|
||||
Map<String, dynamic>.from(response['data']['ticket_data'])
|
||||
];
|
||||
stepMap = getClaimsHistoryList[0];
|
||||
stepKeys = stepMap.keys.toList();
|
||||
final claimsDocs = data['claim_files'];
|
||||
claimFiles = claimsDocs is List
|
||||
? List<Map<String, dynamic>>.from(claimsDocs)
|
||||
: <Map<String, dynamic>>[];
|
||||
|
||||
final isFreeze =
|
||||
response['data']['required_docs']['is_action_freeze'] ?? false;
|
||||
setState(() {
|
||||
isActionFreeze = isFreeze;
|
||||
});
|
||||
final requiredDocs = response['data']['required_docs']['docs'];
|
||||
requiredDocsList = List<Map<String, dynamic>>.from(requiredDocs);
|
||||
_parseTicketData(data);
|
||||
|
||||
final requiredDocsRoot = data['required_docs'];
|
||||
if (requiredDocsRoot is Map) {
|
||||
isActionFreeze = requiredDocsRoot['is_action_freeze'] ?? false;
|
||||
final requiredDocs = requiredDocsRoot['docs'];
|
||||
requiredDocsList = requiredDocs is List
|
||||
? List<Map<String, dynamic>>.from(requiredDocs)
|
||||
: <Map<String, dynamic>>[];
|
||||
} else {
|
||||
isActionFreeze = false;
|
||||
requiredDocsList = <Map<String, dynamic>>[];
|
||||
}
|
||||
|
||||
// ⭐ Make a backup copy to restore later
|
||||
requiredDocsListBackup = requiredDocsList
|
||||
@ -317,6 +325,60 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
}
|
||||
}
|
||||
|
||||
void _parseTicketData(Map<String, dynamic> data) {
|
||||
final ticketDataRaw = data['ticket_data'];
|
||||
final ticketHistoryRaw = data['ticket_history'];
|
||||
|
||||
useStatusKeyedTicketData = false;
|
||||
useListTicketHistory = false;
|
||||
stepMap = <String, dynamic>{};
|
||||
stepKeys = [];
|
||||
getClaimsHistoryList = [];
|
||||
|
||||
if (ticketDataRaw is Map &&
|
||||
!ticketDataRaw.containsKey('status') &&
|
||||
ticketDataRaw.values.any((value) => value is Map)) {
|
||||
useStatusKeyedTicketData = true;
|
||||
stepMap = Map<String, dynamic>.from(ticketDataRaw);
|
||||
stepKeys = stepMap.keys.map((key) => key.toString()).toList();
|
||||
getClaimsHistoryList = [stepMap];
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticketDataRaw is List && ticketDataRaw.isNotEmpty) {
|
||||
useListTicketHistory = true;
|
||||
getClaimsHistoryList = ticketDataRaw
|
||||
.whereType<Map>()
|
||||
.map((item) => Map<String, dynamic>.from(item))
|
||||
.toList();
|
||||
stepKeys = List.generate(getClaimsHistoryList.length, (index) => '$index');
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticketHistoryRaw is List && ticketHistoryRaw.isNotEmpty) {
|
||||
useListTicketHistory = true;
|
||||
getClaimsHistoryList = ticketHistoryRaw
|
||||
.whereType<Map>()
|
||||
.map((item) => Map<String, dynamic>.from(item))
|
||||
.toList();
|
||||
stepKeys = List.generate(getClaimsHistoryList.length, (index) => '$index');
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticketDataRaw is Map && ticketDataRaw.isNotEmpty) {
|
||||
useListTicketHistory = true;
|
||||
getClaimsHistoryList = [Map<String, dynamic>.from(ticketDataRaw)];
|
||||
stepKeys = const ['0'];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _asStringMap(dynamic value) {
|
||||
if (value is Map) {
|
||||
return Map<String, dynamic>.from(value);
|
||||
}
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
|
||||
Future<void> _launchURL(String url) async {
|
||||
final Uri uri = Uri.parse(url);
|
||||
try {
|
||||
@ -660,13 +722,23 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: List.generate(stepKeys.length, (index) {
|
||||
String stepTitleKey = stepKeys[index];
|
||||
Map<String, dynamic> stepData = stepMap[stepTitleKey];
|
||||
Widget content = _getStepContentFromApi(stepData);
|
||||
if (useListTicketHistory) {
|
||||
final stepData = getClaimsHistoryList[index];
|
||||
return _buildStep(
|
||||
stepNumber: index + 1,
|
||||
title: _getStepTitleFromHistoryItem(stepData),
|
||||
content: _getStepContentFromHistoryItem(stepData),
|
||||
isLast: index == stepKeys.length - 1,
|
||||
);
|
||||
}
|
||||
|
||||
final stepTitleKey = stepKeys[index];
|
||||
final stepData = _asStringMap(stepMap[stepTitleKey]);
|
||||
return _buildStep(
|
||||
stepNumber: index + 1,
|
||||
title: _getStepTitleFromApi(stepTitleKey, stepData),
|
||||
content: content,
|
||||
title: _getStepTitleFromApi(
|
||||
stepTitleKey, stepData),
|
||||
content: _getStepContentFromApi(stepData),
|
||||
isLast: index == stepKeys.length - 1,
|
||||
);
|
||||
}),
|
||||
@ -1459,6 +1531,53 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getStepTitleFromHistoryItem(Map<String, dynamic> data) {
|
||||
final title =
|
||||
(data['display_name'] ?? data['field_name'] ?? 'Update').toString();
|
||||
final modifiedBy = (data['modified_by'] ?? '').toString();
|
||||
final modifiedAt =
|
||||
(data['created_at'] ?? data['modified_at'] ?? '').toString();
|
||||
final symbol = modifiedBy.isNotEmpty ? ' - ' : '';
|
||||
final isDesktop = Responsive.isDesktop(context);
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: title + (isDesktop ? ' ' : '\n'),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: Responsive.isDesktop(context) ? 15 : 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF212120)),
|
||||
),
|
||||
TextSpan(
|
||||
text: modifiedAt.isEmpty ? '' : ' ($modifiedBy$symbol$modifiedAt)',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: Responsive.isDesktop(context) ? 14 : 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF565656)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getStepContentFromHistoryItem(Map<String, dynamic> data) {
|
||||
final oldValue =
|
||||
(data['old_status_value'] ?? data['old_value'] ?? '-').toString();
|
||||
final newValue =
|
||||
(data['new_status_value'] ?? data['new_value'] ?? '-').toString();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHistoryListData('Previous Value', oldValue),
|
||||
const SizedBox(height: 1),
|
||||
_buildHistoryListData('Updated Value', newValue),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getStepTitleFromApi(String status, Map<String, dynamic> data) {
|
||||
final modifiedBy = data['modified_by'] ?? '';
|
||||
final modifiedAt = data['modified_at'] ?? '';
|
||||
@ -1489,14 +1608,36 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
}
|
||||
|
||||
Widget _getStepContentFromApi(Map<String, dynamic> data) {
|
||||
List<Widget> rows = [];
|
||||
data.forEach((key, value) {
|
||||
if (key == 'modified_by' || key == 'modified_at') return;
|
||||
String displayName = value['display_name'] ?? key;
|
||||
String displayValue = value['display_value'] ?? 'N/A';
|
||||
rows.add(_buildHistoryListData(displayName, displayValue));
|
||||
rows.add(SizedBox(height: 1));
|
||||
});
|
||||
final rows = <Widget>[];
|
||||
|
||||
for (final entry in data.entries) {
|
||||
final key = entry.key.toString();
|
||||
final value = entry.value;
|
||||
|
||||
if (key == 'modified_by' || key == 'modified_at') continue;
|
||||
|
||||
if (value is Map) {
|
||||
final nested = Map<String, dynamic>.from(value);
|
||||
final displayName =
|
||||
(nested['display_name'] ?? key).toString();
|
||||
final displayValue =
|
||||
(nested['display_value'] ?? 'N/A').toString();
|
||||
rows
|
||||
..add(_buildHistoryListData(displayName, displayValue))
|
||||
..add(const SizedBox(height: 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == 'reason') {
|
||||
final reason = value?.toString().trim() ?? '';
|
||||
if (reason.isNotEmpty) {
|
||||
rows
|
||||
..add(_buildReasonText(reason))
|
||||
..add(const SizedBox(height: 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: rows);
|
||||
}
|
||||
|
||||
@ -1550,6 +1691,24 @@ class _ClaimHistoryPopupState extends State<ClaimHistoryPopup>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReasonText(String value) {
|
||||
final displayValue =
|
||||
(value.trim().isEmpty) ? 'N/A' : value.trim();
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
displayValue,
|
||||
textAlign: TextAlign.left,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF000000),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Responsive.isDesktop(context) ? 14 : 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHistoryListData(String title, String value) {
|
||||
final displayValue =
|
||||
(value == null || value.trim().isEmpty) ? 'N/A' : value;
|
||||
|
||||
@ -58,6 +58,7 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
bool isSubmitting = false;
|
||||
|
||||
bool isActionFreeze = false;
|
||||
bool useStatusKeyedTicketData = false;
|
||||
|
||||
// IR Docs state
|
||||
bool showIRDocs = false;
|
||||
@ -286,20 +287,34 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
|
||||
final ticketDataRaw = root['ticket_data'] ?? nestedData['ticket_data'];
|
||||
if (ticketDataRaw is List) {
|
||||
useStatusKeyedTicketData = false;
|
||||
getClaimsHistoryList = ticketDataRaw
|
||||
.map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e))
|
||||
.toList();
|
||||
stepMap = getClaimsHistoryList.isNotEmpty
|
||||
? getClaimsHistoryList.first
|
||||
: <String, dynamic>{};
|
||||
stepKeys = List.generate(getClaimsHistoryList.length, (i) => '$i');
|
||||
} else if (ticketDataRaw is Map) {
|
||||
getClaimsHistoryList = [Map<String, dynamic>.from(ticketDataRaw)];
|
||||
final map = Map<String, dynamic>.from(ticketDataRaw);
|
||||
if (map.containsKey('status')) {
|
||||
useStatusKeyedTicketData = false;
|
||||
getClaimsHistoryList = [map];
|
||||
stepMap = map;
|
||||
stepKeys = const ['0'];
|
||||
} else {
|
||||
useStatusKeyedTicketData = true;
|
||||
stepMap = map;
|
||||
stepKeys = map.keys.map((k) => k.toString()).toList();
|
||||
getClaimsHistoryList = [map];
|
||||
}
|
||||
} else {
|
||||
useStatusKeyedTicketData = false;
|
||||
getClaimsHistoryList = [];
|
||||
stepMap = <String, dynamic>{};
|
||||
stepKeys = [];
|
||||
}
|
||||
|
||||
stepMap = getClaimsHistoryList.isNotEmpty
|
||||
? getClaimsHistoryList.first
|
||||
: <String, dynamic>{};
|
||||
stepKeys = List.generate(getClaimsHistoryList.length, (i) => '$i');
|
||||
|
||||
final claimsDocs = root['claims_files'] ??
|
||||
root['claim_files'] ??
|
||||
nestedData['claims_files'] ??
|
||||
@ -688,13 +703,25 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
child: getClaimsHistoryList.isNotEmpty
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: List.generate(getClaimsHistoryList.length, (index) {
|
||||
children: useStatusKeyedTicketData
|
||||
? List.generate(stepKeys.length, (index) {
|
||||
final stepTitleKey = stepKeys[index];
|
||||
final stepData = Map<String, dynamic>.from(
|
||||
stepMap[stepTitleKey] ?? <String, dynamic>{});
|
||||
return _buildStep(
|
||||
stepNumber: index + 1,
|
||||
title: _getStepTitleFromStatusKey(
|
||||
stepTitleKey, stepData),
|
||||
content: _getStepContentFromApi(stepData),
|
||||
isLast: index == stepKeys.length - 1,
|
||||
);
|
||||
})
|
||||
: List.generate(getClaimsHistoryList.length, (index) {
|
||||
final stepData = getClaimsHistoryList[index];
|
||||
Widget content = _getStepContentFromApi(stepData);
|
||||
return _buildStep(
|
||||
stepNumber: index + 1,
|
||||
title: _getStepTitleFromApi(stepData),
|
||||
content: content,
|
||||
content: _getStepContentFromApi(stepData),
|
||||
isLast: index == getClaimsHistoryList.length - 1,
|
||||
);
|
||||
}),
|
||||
@ -1487,6 +1514,35 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getStepTitleFromStatusKey(String status, Map<String, dynamic> data) {
|
||||
final modifiedBy = (data['modified_by'] ?? '').toString();
|
||||
final modifiedAt =
|
||||
(data['modified_at'] ?? data['changed_at'] ?? '').toString();
|
||||
final symbol = modifiedBy.isNotEmpty ? ' - ' : '';
|
||||
final isDesktop = Responsive.isDesktop(context);
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: status + (isDesktop ? ' ' : '\n'),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: Responsive.isDesktop(context) ? 15 : 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF212120)),
|
||||
),
|
||||
TextSpan(
|
||||
text: modifiedAt.isEmpty ? '' : ' ($modifiedBy$symbol$modifiedAt)',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: Responsive.isDesktop(context) ? 14 : 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF565656)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getStepTitleFromApi(Map<String, dynamic> data) {
|
||||
final status = (data['status'] ?? '').toString();
|
||||
final changedAt =
|
||||
@ -1526,11 +1582,22 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
key == 'changed_at') {
|
||||
return;
|
||||
}
|
||||
if (value is! Map) return;
|
||||
String displayName = value['display_name'] ?? key;
|
||||
String displayValue = value['display_value'] ?? 'N/A';
|
||||
rows.add(_buildHistoryListData(displayName, displayValue));
|
||||
rows.add(SizedBox(height: 1));
|
||||
|
||||
if (value is Map) {
|
||||
final displayName = value['display_name']?.toString() ?? key;
|
||||
final displayValue = value['display_value']?.toString() ?? 'N/A';
|
||||
rows.add(_buildHistoryListData(displayName, displayValue));
|
||||
rows.add(const SizedBox(height: 1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == 'reason' && value != null) {
|
||||
final reason = value.toString().trim();
|
||||
if (reason.isNotEmpty) {
|
||||
rows.add(_buildReasonText(reason));
|
||||
rows.add(const SizedBox(height: 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: rows);
|
||||
}
|
||||
@ -1585,6 +1652,24 @@ class _NonEBClaimsHistoryState extends State<NonEBClaimsHistory>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReasonText(String value) {
|
||||
final displayValue =
|
||||
(value.trim().isEmpty) ? 'N/A' : value.trim();
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
displayValue,
|
||||
textAlign: TextAlign.left,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF000000),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Responsive.isDesktop(context) ? 14 : 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHistoryListData(String title, String value) {
|
||||
final displayValue =
|
||||
(value == null || value.trim().isEmpty) ? 'N/A' : value;
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:universal_html/html.dart' as html;
|
||||
import '../service/secure_pop_scope.dart';
|
||||
import 'claimshistory.dart';
|
||||
import '../customAppBar/base_layout.dart';
|
||||
import '../customAppBar/toastHelper.dart';
|
||||
import '../service/api_service.dart';
|
||||
import '../service/token_storage_service.dart';
|
||||
import 'RaiseClaimForm.dart';
|
||||
@ -81,34 +82,18 @@ class _NonEBClaimsListState extends State<NonEBClaimsList> {
|
||||
int? appliedClaimStatus;
|
||||
|
||||
Color getStatusColor(String status) {
|
||||
switch (status.toLowerCase().trim()) {
|
||||
case 'claim intimation received':
|
||||
return const Color(0xFF4A90E2);
|
||||
case 'claim intimated':
|
||||
return const Color(0xFF5B9BD5);
|
||||
case 'insurer pending':
|
||||
return const Color(0xFFF5A623);
|
||||
case 'surveyor pending':
|
||||
return const Color(0xFFFF8C42);
|
||||
case 'insured pending':
|
||||
return const Color(0xFFE67E22);
|
||||
case 'insurer / surveyor pending':
|
||||
return const Color(0xFFD35400);
|
||||
case 'workshop pending':
|
||||
return const Color(0xFF9B59B6);
|
||||
case 'claim approved':
|
||||
return const Color(0xFF2ECC71);
|
||||
case 'claim settled':
|
||||
return const Color(0xFF1ABC9C);
|
||||
case 'claim closed':
|
||||
return const Color(0xFF95A5A6);
|
||||
case 'claim rejected':
|
||||
return const Color(0xFFE74C3C);
|
||||
case 'claim withdrawn':
|
||||
return const Color(0xFF7F8C8D);
|
||||
default:
|
||||
return const Color(0xFFB0BEC5);
|
||||
final normalized = status.toLowerCase().trim();
|
||||
if (normalized == 'rejected' ||
|
||||
normalized == 'claim rejected' ||
|
||||
normalized.contains('rejected')) {
|
||||
return const Color(0xFFE26728); // Export button orange
|
||||
}
|
||||
if (normalized == 'settled' ||
|
||||
normalized == 'claim settled' ||
|
||||
normalized.contains('settled')) {
|
||||
return const Color(0xFF2ECC71); // Green
|
||||
}
|
||||
return const Color(0xFF00A6A6); // List header color
|
||||
}
|
||||
|
||||
Map<String, dynamic> claim_Detials() {
|
||||
@ -169,7 +154,22 @@ class _NonEBClaimsListState extends State<NonEBClaimsList> {
|
||||
// }
|
||||
|
||||
// getApiData();
|
||||
_loadIds();
|
||||
_checkToken();
|
||||
}
|
||||
|
||||
Future<void> _checkToken() async {
|
||||
final token = await tokenService.getCurrentToken();
|
||||
if (token == null || token.trim().isEmpty) {
|
||||
if (!mounted) return;
|
||||
ToastHelper.showErrorToast(context, 'Session Out');
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _loadIds();
|
||||
}
|
||||
|
||||
Future<void> _loadIds() async {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:nhancepolicy/customAppBar/customAppBar.dart';
|
||||
|
||||
@ -137,6 +137,15 @@ class TokenStorageService {
|
||||
key: _branchNameKey,
|
||||
value: branch['branch_name']?.toString() ?? '',
|
||||
);
|
||||
|
||||
// Prefer claims_sub_menu from branch payload when available.
|
||||
final claimsSubMenu = branch['claims_sub_menu'];
|
||||
if (claimsSubMenu is List) {
|
||||
await _secureStorage.write(
|
||||
key: 'claims_sub_menu',
|
||||
value: jsonEncode(claimsSubMenu),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔓 Decode JWT
|
||||
@ -225,6 +234,15 @@ class TokenStorageService {
|
||||
|
||||
// ================= TOKEN =================
|
||||
await _secureStorage.write(key: 'token', value: token);
|
||||
|
||||
// ================= CLAIMS SUB MENU =================
|
||||
if (decodedToken.containsKey('claims_sub_menu')) {
|
||||
final claimsSubMenu = decodedToken['claims_sub_menu'];
|
||||
await _secureStorage.write(
|
||||
key: 'claims_sub_menu',
|
||||
value: jsonEncode(claimsSubMenu is List ? claimsSubMenu : []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> readValue(String key) async {
|
||||
@ -257,6 +275,7 @@ class TokenStorageService {
|
||||
'enrollmentEmpPrimaryId',
|
||||
'enrollmentHrId',
|
||||
'token',
|
||||
'claims_sub_menu',
|
||||
];
|
||||
|
||||
for (final key in keysToRemove) {
|
||||
|
||||
@ -53,7 +53,7 @@ dependencies:
|
||||
flutter_animated_button: ^2.0.3
|
||||
spreadsheet_decoder: ^2.2.0
|
||||
url_launcher: ^6.2.6
|
||||
font_awesome_flutter: ^10.7.0
|
||||
font_awesome_flutter: ^11.0.0
|
||||
archive: ^3.4.9
|
||||
dropdown_search: ^6.0.2
|
||||
flutter_secure_storage: ^10.0.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user