1331 lines
44 KiB
Dart
1331 lines
44 KiB
Dart
import 'dart:convert';
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/gestures.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:jwt_decode/jwt_decode.dart';
|
||
import 'package:nhancepolicy/responsive.dart';
|
||
import 'package:nhancepolicy/service/api_service.dart';
|
||
import 'package:nhancepolicy/service/hrDashboardTabs/activePolicies.dart';
|
||
import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart';
|
||
import 'package:nhancepolicy/presentation/claims.dart';
|
||
import 'package:nhancepolicy/presentation/postFileUpload.dart';
|
||
import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart';
|
||
import 'package:nhancepolicy/service/token_storage_service.dart';
|
||
import 'package:nhancepolicy/service/_ResponsiveGridConfig.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import 'package:universal_html/html.dart' as html;
|
||
import 'package:intl/intl.dart';
|
||
|
||
import '../config/environment.dart';
|
||
import '../customAppBar/base_layout.dart';
|
||
import '../customAppBar/toastHelper.dart';
|
||
import '../service/secure_pop_scope.dart';
|
||
import 'hrPolicyDetails.dart';
|
||
import 'package:nhancepolicy/logger.dart';
|
||
|
||
class policies extends StatefulWidget {
|
||
const policies({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
State<policies> createState() => _policiesState();
|
||
}
|
||
|
||
class _policiesState extends State<policies>
|
||
with SingleTickerProviderStateMixin {
|
||
late ApiService apiService;
|
||
bool isLoading = false;
|
||
int isHrcode = 0;
|
||
|
||
String? _postPreToken = '';
|
||
|
||
dynamic enrollmentClient_id;
|
||
dynamic policy_name;
|
||
dynamic getPreCardArrays = [];
|
||
dynamic getPostCardArrays = [];
|
||
|
||
dynamic empClientBranchId;
|
||
dynamic empHrId;
|
||
int stausVal = 1;
|
||
int selectedIndex = 1;
|
||
|
||
dynamic enrollmentEmpClientBranchId;
|
||
dynamic enrollmentHrId;
|
||
dynamic empClientId;
|
||
String empCodeFromHrPolcy = '';
|
||
|
||
List<Map<String, dynamic>> openForEnrollmentList = [];
|
||
List<Map<String, dynamic>> activePoliciesList = [];
|
||
|
||
final tokenService = TokenStorageService();
|
||
List<int> postModules = [];
|
||
List<int> enrollmentModules = [];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
apiService = ApiService(context); // Initialize ApiService here
|
||
checkToken();
|
||
// empCodeFromHrPolcy = widget.empCodeFromHrPolicy;
|
||
|
||
// _tabController = TabController(length: 4, vsync: this);
|
||
// _tabController.addListener(() {
|
||
// setState(() {
|
||
// selectedIndex = _tabController.index;
|
||
// });
|
||
// });
|
||
// Future.delayed(Duration(seconds: 3), () {
|
||
// setState(() {
|
||
// isLoading = false;
|
||
// });
|
||
// });
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
super.dispose();
|
||
}
|
||
|
||
checkToken() async {
|
||
// enrollToken = prefs.getString('pre_enrollment_data');
|
||
// _postToken = prefs.getString('post_enrollment_data');
|
||
|
||
// Get the current token
|
||
final token = await tokenService.getCurrentToken();
|
||
logDebug('token - $token');
|
||
|
||
logDebug('token check in');
|
||
if ((token != null && token!.isNotEmpty)) {
|
||
logDebug('token check done');
|
||
_loadToken();
|
||
} else {
|
||
logDebug('token check reject');
|
||
ToastHelper.showErrorToast(context, 'Session Out');
|
||
Navigator.pushReplacementNamed(context, 'hrLogin');
|
||
}
|
||
}
|
||
|
||
// Future<void> _loadToken() async {
|
||
// final enrollmentRaw = await tokenService.readValue('enrollmentAllowed_modules'); // "[1]"
|
||
// final postRaw = await tokenService.readValue('empAllowed_modules'); // "[2,3,4]"
|
||
//
|
||
// // ✅ Decode safely
|
||
// enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty
|
||
// ? List<int>.from(jsonDecode(enrollmentRaw))
|
||
// : [];
|
||
//
|
||
// postModules = postRaw != null && postRaw.isNotEmpty
|
||
// ? List<int>.from(jsonDecode(postRaw))
|
||
// : [];
|
||
//
|
||
// logDebug('enrollmentModules $enrollmentModules');
|
||
// logDebug('postModules $postModules');
|
||
//
|
||
// _postPreToken = await tokenService.getCurrentToken();
|
||
// logDebug(_postPreToken);
|
||
//
|
||
// if (enrollmentModules.contains(1)) {
|
||
// enrollmentClient_id = await tokenService.readValue('enrollmentClient_id');
|
||
// enrollmentEmpClientBranchId =
|
||
// await tokenService.readValue('enrollmentEmpClientBranchId');
|
||
// enrollmentHrId = await tokenService.readValue('enrollmentHrId');
|
||
//
|
||
// await getPreCashDepositDetails(enrollmentEmpClientBranchId,
|
||
// enrollmentClient_id, enrollmentHrId, _postPreToken);
|
||
// }
|
||
//
|
||
// if (postModules.contains(2)) {
|
||
// empClientId = await tokenService.readValue('empClientId');
|
||
// empClientBranchId = await tokenService.readValue('empClientBranchId');
|
||
// empHrId = await tokenService.readValue('empHrId');
|
||
//
|
||
// await getPostCashDepositDetails(empClientBranchId, empClientId, empHrId, _postPreToken);
|
||
// }
|
||
//
|
||
// }
|
||
|
||
Future<void> _loadToken() async {
|
||
setState(() {
|
||
isLoading = true; // 🔥 START LOADER HERE
|
||
});
|
||
|
||
try {
|
||
final enrollmentRaw =
|
||
await tokenService.readValue('enrollmentAllowed_modules');
|
||
final postRaw = await tokenService.readValue('empAllowed_modules');
|
||
|
||
enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty
|
||
? List<int>.from(jsonDecode(enrollmentRaw))
|
||
: [];
|
||
|
||
postModules = postRaw != null && postRaw.isNotEmpty
|
||
? List<int>.from(jsonDecode(postRaw))
|
||
: [];
|
||
|
||
_postPreToken = await tokenService.getCurrentToken();
|
||
|
||
List<Future> apiCalls = [];
|
||
|
||
/// 👇 Add APIs dynamically
|
||
if (enrollmentModules.contains(1)) {
|
||
enrollmentClient_id =
|
||
await tokenService.readValue('enrollmentClient_id');
|
||
enrollmentEmpClientBranchId =
|
||
await tokenService.readValue('enrollmentEmpClientBranchId');
|
||
enrollmentHrId = await tokenService.readValue('enrollmentHrId');
|
||
|
||
apiCalls.add(
|
||
getPreCashDepositDetails(
|
||
enrollmentEmpClientBranchId,
|
||
enrollmentClient_id,
|
||
enrollmentHrId,
|
||
_postPreToken,
|
||
),
|
||
);
|
||
}
|
||
|
||
if (postModules.contains(2)) {
|
||
empClientId = await tokenService.readValue('empClientId');
|
||
empClientBranchId = await tokenService.readValue('empClientBranchId');
|
||
empHrId = await tokenService.readValue('empHrId');
|
||
|
||
apiCalls.add(
|
||
getPostCashDepositDetails(
|
||
empClientBranchId,
|
||
empClientId,
|
||
empHrId,
|
||
_postPreToken,
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 🔥 WAIT FOR ALL APIs
|
||
await Future.wait(apiCalls);
|
||
} catch (e) {
|
||
logDebug("Error in _loadToken: $e");
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() {
|
||
isLoading = false; // 🔥 STOP LOADER ONLY ONCE
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> getPreCashDepositDetails(enrollmentEmpClientBranchId,
|
||
enrollmentClient_id, enrollmentHrId, _postPreToken) async {
|
||
logDebug('IN');
|
||
logDebug("clintBranchId -$enrollmentEmpClientBranchId");
|
||
logDebug("clintID -$enrollmentClient_id");
|
||
logDebug("hr_id -$enrollmentHrId");
|
||
logDebug("token -$_postPreToken");
|
||
|
||
// setState(() {
|
||
// _isLoading = true;
|
||
// });
|
||
try {
|
||
if (enrollmentEmpClientBranchId == null || enrollmentClient_id == null) {
|
||
return;
|
||
}
|
||
final response = await apiService.getCashDepositDetailsToApi(
|
||
enrollmentClient_id!,
|
||
enrollmentEmpClientBranchId!,
|
||
enrollmentHrId,
|
||
_postPreToken);
|
||
|
||
// final response = await apiService.getCashDepositDetailsToApi(
|
||
// clintID!, clintBranchId!, hr_id, token);
|
||
logDebug('IN1');
|
||
if (response['status'] == 'success') {
|
||
setState(() {
|
||
logDebug('response');
|
||
logDebug(response['data']);
|
||
getPreCardArrays = List<Map<String, dynamic>>.from(response['data']);
|
||
logDebug('getPreCardArrays');
|
||
logDebug(getPreCardArrays);
|
||
openForEnrollmentList = getPreCardArrays;
|
||
});
|
||
|
||
logDebug('IN2');
|
||
} else {
|
||
logDebug('API request failed with status');
|
||
}
|
||
} catch (e) {
|
||
logDebug('Exception occurred: $e');
|
||
}
|
||
}
|
||
|
||
Future<void> getPostCashDepositDetails(
|
||
empClientBranchId, empClientId, empHrId, _postPreToken) async {
|
||
logDebug('IN');
|
||
logDebug("clintBranchId -$empClientBranchId");
|
||
logDebug("clintID -$empClientId");
|
||
logDebug("hr_id -$empHrId");
|
||
logDebug("token -$_postPreToken");
|
||
|
||
// setState(() {
|
||
// _isLoading = true;
|
||
// });
|
||
try {
|
||
if (empClientBranchId == null || empClientId == null) {
|
||
return;
|
||
}
|
||
final response = await apiService.getActiveCashDepositDetailsToApi(
|
||
empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal);
|
||
|
||
// final response = await apiService.getCashDepositDetailsToApi(
|
||
// clintID!, clintBranchId!, hr_id, token);
|
||
logDebug('IN1');
|
||
if (response['status'] == 'success') {
|
||
setState(() {
|
||
logDebug('response');
|
||
logDebug(response['data']);
|
||
getPostCardArrays = List<Map<String, dynamic>>.from(response['data']);
|
||
logDebug('getPostCardArrays');
|
||
logDebug(getPostCardArrays);
|
||
|
||
activePoliciesList = getPostCardArrays;
|
||
});
|
||
|
||
logDebug('IN2');
|
||
} else {
|
||
logDebug('API request failed with status');
|
||
setState(() {
|
||
activePoliciesList = [];
|
||
});
|
||
logDebug('API request failed with status');
|
||
}
|
||
} catch (e) {
|
||
logDebug('Exception occurred: $e');
|
||
}
|
||
}
|
||
|
||
Future<bool> _showLogoutDialog() async {
|
||
return await showDialog<bool>(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) => AlertDialog(
|
||
title: Text("Confirm Logout"),
|
||
content: Text("Do you want to logout?"),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.of(context).pop(false),
|
||
child: Text("Cancel"),
|
||
),
|
||
TextButton(
|
||
onPressed: () => Navigator.of(context).pop(true),
|
||
child: Text("Logout"),
|
||
),
|
||
],
|
||
),
|
||
) ??
|
||
false;
|
||
}
|
||
|
||
Future<void> getEcardBulkDownload(clientPolicyId) async {
|
||
try {
|
||
logDebug('10');
|
||
empHrId = await tokenService.readValue('empHrId');
|
||
final response = await apiService.getEcardBulkDownloadApi(
|
||
clientPolicyId, empHrId, '', _postPreToken!);
|
||
if (response['status'] == true) {
|
||
logDebug('Request success');
|
||
_showBulkDownloadSuccessPopup(response['message']);
|
||
} else {
|
||
ToastHelper.showErrorToast(context, response['message']);
|
||
logDebug('Request failed with status: ${response['code']}');
|
||
}
|
||
} catch (e) {
|
||
logDebug('Exception occurred: $e');
|
||
}
|
||
}
|
||
|
||
void _showBulkDownloadSuccessPopup(String message) {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) {
|
||
return AlertDialog(
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(
|
||
Icons.check_circle,
|
||
color: Color(0xFF009195),
|
||
size: 60,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
message,
|
||
textAlign: TextAlign.center,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: ElevatedButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF009195),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: Text(
|
||
'OK',
|
||
style: GoogleFonts.poppins(color: Colors.white),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return BaseLayout(
|
||
child: SecurePopScope(
|
||
child: buildPoliciesBody(
|
||
openEnrollment: openForEnrollmentList,
|
||
activePolicies: activePoliciesList,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget buildPoliciesBody({
|
||
required List<Map<String, dynamic>> openEnrollment,
|
||
required List<Map<String, dynamic>> activePolicies,
|
||
}) {
|
||
return isLoading
|
||
? Container(
|
||
color: Colors.transparent, // Semi-transparent background
|
||
child: Center(
|
||
child: // Your GIF loader widget
|
||
Image.asset(
|
||
height: 60,
|
||
width: 60,
|
||
'assets/nhance-loader.gif'), // Adjust path to your GIF loader
|
||
),
|
||
)
|
||
: Scaffold(
|
||
body: SingleChildScrollView(
|
||
|
||
// padding: const EdgeInsets.all(20),
|
||
child: Container(
|
||
color: const Color(0xFFF5F7F7), // 👈 same light grey as dashboard
|
||
// padding: const EdgeInsets.all(20),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
// IconButton(
|
||
// onPressed: () {
|
||
// _showLogoutDialog();
|
||
// },
|
||
// icon: const Icon(
|
||
// Icons.arrow_back_ios,
|
||
// size: 20,
|
||
// color: Colors.black,
|
||
// ),
|
||
// padding: EdgeInsets.zero,
|
||
// constraints: const BoxConstraints(),
|
||
// ),
|
||
const SizedBox(width: 5),
|
||
Text(
|
||
'Policies',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 22,
|
||
fontWeight: FontWeight.w500,
|
||
color: Colors.black,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (enrollmentModules.contains(1)) ...[
|
||
SizedBox(height: 15),
|
||
Container(
|
||
width: double.infinity,
|
||
// constraints: const BoxConstraints(
|
||
// minHeight: 180,
|
||
// ),
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(6),
|
||
),
|
||
child: IntrinsicHeight(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Open for Enrollment',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14, fontWeight: FontWeight.w600),
|
||
),
|
||
SizedBox(height: 14),
|
||
|
||
/// ✅ SCROLLABLE AREA
|
||
openEnrollment.isEmpty
|
||
? _EmptyBox('No policies open for enrollment')
|
||
: _PolicyGrid(
|
||
policies: openEnrollment,
|
||
isEnrollment: true,
|
||
),
|
||
],
|
||
),
|
||
)),
|
||
],
|
||
if (postModules.contains(2)) ...[
|
||
SizedBox(height: 20),
|
||
Container(
|
||
width: double.infinity,
|
||
// constraints: const BoxConstraints(
|
||
// minHeight: 180,
|
||
// ),
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(6),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
/// HEADER
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
'Active Policies',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14, fontWeight: FontWeight.w600),
|
||
),
|
||
_ActiveExpiredToggle(
|
||
selectedIndex: selectedIndex,
|
||
onChange: (value) async {
|
||
setState(() {
|
||
selectedIndex = value;
|
||
stausVal = value == 1 ? 1 : 0;
|
||
});
|
||
await getPostCashDepositDetails(
|
||
empClientBranchId,
|
||
empClientId,
|
||
empHrId,
|
||
_postPreToken,
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 14),
|
||
|
||
/// ✅ SCROLLABLE GRID
|
||
activePolicies.isEmpty
|
||
? stausVal == 0
|
||
? _EmptyBox(
|
||
'You don’t have any expired policies at the moment.')
|
||
: _EmptyBox('No active policies found')
|
||
: _PolicyGrid(
|
||
policies: activePolicies,
|
||
isEnrollment: false,
|
||
onBulkDownload: getEcardBulkDownload,
|
||
),
|
||
|
||
const SizedBox(height: 8),
|
||
Align(
|
||
alignment: Alignment.bottomRight,
|
||
child: Text(
|
||
'* Premium may vary subject to claims',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 10, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
]
|
||
],
|
||
),
|
||
)),
|
||
);
|
||
}
|
||
}
|
||
|
||
class ResponsiveGridConfig {
|
||
final int crossAxisCount;
|
||
final double childAspectRatio;
|
||
|
||
const ResponsiveGridConfig(
|
||
this.crossAxisCount,
|
||
this.childAspectRatio,
|
||
);
|
||
}
|
||
|
||
class _PolicyGrid extends StatelessWidget {
|
||
final List<Map<String, dynamic>> policies;
|
||
final bool isEnrollment;
|
||
final Function(String clientPolicyId)? onBulkDownload;
|
||
|
||
const _PolicyGrid({
|
||
super.key,
|
||
required this.policies,
|
||
required this.isEnrollment,
|
||
this.onBulkDownload,
|
||
});
|
||
|
||
bool _isNonEbPolicy(Map<String, dynamic> data) {
|
||
final allocg = (data['allocg'] ?? '').toString().trim().toLowerCase();
|
||
return allocg == 'non-eb';
|
||
}
|
||
|
||
ResponsiveGridConfig _getGridConfig(
|
||
BuildContext context,
|
||
bool isEnrollment,
|
||
) {
|
||
final width = MediaQuery.of(context).size.width;
|
||
|
||
// if (width < 600) {
|
||
// return ResponsiveGridConfig(1, isEnrollment ? 1.25 : 1.15);
|
||
// } else if (width < 900) {
|
||
// return ResponsiveGridConfig(2, isEnrollment ? 1.6 : 1.45);
|
||
// } else if (width < 1400) {
|
||
// return ResponsiveGridConfig(3, isEnrollment ? 3.1 : 2.5);
|
||
// } else {
|
||
// return ResponsiveGridConfig(4, isEnrollment ? 3.1 : 2.4);
|
||
// }
|
||
|
||
if (width >= 1400) {
|
||
return const ResponsiveGridConfig(4, 2.6); // Big screen
|
||
} else if (width >= 1000) {
|
||
return const ResponsiveGridConfig(3, 2.3); // Small desktop
|
||
} else if (width >= 600) {
|
||
return const ResponsiveGridConfig(2, 2.0); // Tablet
|
||
} else {
|
||
return const ResponsiveGridConfig(1, 1.8); // Mobile
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final tokenService = TokenStorageService();
|
||
final config = _getGridConfig(context, isEnrollment);
|
||
|
||
/// ✅ Enrollment → No change
|
||
if (isEnrollment) {
|
||
return _buildGrid(
|
||
context,
|
||
policies,
|
||
config,
|
||
tokenService,
|
||
);
|
||
}
|
||
|
||
/// ✅ Active → Split EB / Non-EB
|
||
final ebPolicies = policies.where((p) => p['allocg'] == 'EB').toList();
|
||
|
||
final nonEbPolicies =
|
||
policies.where((p) => p['allocg'] == 'Non-EB').toList();
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
/// ✅ EB section
|
||
if (ebPolicies.isNotEmpty) ...[
|
||
Text(
|
||
'EB Policies',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFF009195),
|
||
),
|
||
),
|
||
const SizedBox(height: 10),
|
||
_buildGrid(
|
||
context,
|
||
ebPolicies,
|
||
config,
|
||
tokenService,
|
||
),
|
||
],
|
||
|
||
/// ✅ Non-EB section
|
||
if (nonEbPolicies.isNotEmpty) ...[
|
||
Text(
|
||
'Non-EB Policies',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFFD06A00),
|
||
),
|
||
),
|
||
const SizedBox(height: 10),
|
||
_buildGrid(
|
||
context,
|
||
nonEbPolicies,
|
||
config,
|
||
tokenService,
|
||
isNonEb: true,
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildGrid(
|
||
BuildContext context,
|
||
List<Map<String, dynamic>> list,
|
||
ResponsiveGridConfig config,
|
||
TokenStorageService tokenService, {
|
||
bool isNonEb = false,
|
||
}) {
|
||
final int rowCount = (list.length / config.crossAxisCount).ceil();
|
||
|
||
double cardHeight;
|
||
|
||
if (isEnrollment) {
|
||
cardHeight = 140;
|
||
} else {
|
||
cardHeight = 170;
|
||
}
|
||
|
||
final double totalHeight = rowCount * cardHeight + ((rowCount - 1) * 16);
|
||
return SizedBox(
|
||
height: totalHeight,
|
||
child: GridView.builder(
|
||
physics: const BouncingScrollPhysics(),
|
||
shrinkWrap: true,
|
||
padding: EdgeInsets.zero,
|
||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: config.crossAxisCount,
|
||
childAspectRatio: config.childAspectRatio,
|
||
crossAxisSpacing: 16,
|
||
mainAxisSpacing: 16,
|
||
mainAxisExtent: isEnrollment ? 120 : 150, // fixed card height
|
||
),
|
||
itemCount: list.length,
|
||
itemBuilder: (context, index) {
|
||
final data = list[index];
|
||
|
||
return isEnrollment
|
||
? _EnrollmentPolicyCardNew(
|
||
data: data,
|
||
onTap: () async {
|
||
final token = await tokenService.getCurrentToken();
|
||
final clientId =
|
||
await tokenService.readValue('enrollmentClient_id');
|
||
final branchId = await tokenService
|
||
.readValue('enrollmentEmpClientBranchId');
|
||
|
||
if (token == null || clientId == null || branchId == null) {
|
||
return;
|
||
}
|
||
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
settings: const RouteSettings(name: 'hrPolicyDetails'),
|
||
builder: (_) => hrPolicyDetails(
|
||
ClientId: clientId,
|
||
policyTypeId: data['policy_type_id'].toString(),
|
||
ClientPoliyId: data['client_policy_id'].toString(),
|
||
clientBranchId: branchId,
|
||
Token: token,
|
||
TokenType: "pre",
|
||
cardType: data['type'].toString(),
|
||
cardPolicyNo: data['policy_no'].toString(),
|
||
cardInsurer_name:
|
||
data['insurer_short_name'].toString(),
|
||
cardPolicy_name: data['policy_name'].toString(),
|
||
cardPolicy_ExpDate:
|
||
data['policy_expiry_date'].toString(),
|
||
total_premium: '',
|
||
is_ecard_bulk_download_for_employee: 0,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
)
|
||
: _ActivePolicyCardNew(
|
||
data: data,
|
||
isNonEb: isNonEb,
|
||
onBulkDownload: onBulkDownload,
|
||
onTap: () async {
|
||
final token = await tokenService.getCurrentToken();
|
||
final clientId =
|
||
await tokenService.readValue('empClientId');
|
||
final branchId =
|
||
await tokenService.readValue('empClientBranchId');
|
||
|
||
logDebug("token: $token");
|
||
logDebug("clientId: $clientId");
|
||
logDebug("branchId: $branchId");
|
||
|
||
if (token == null || clientId == null || branchId == null) {
|
||
logDebug("Missing required values");
|
||
return;
|
||
}
|
||
|
||
if (_isNonEbPolicy(data)) {
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
settings: const RouteSettings(name: 'postFileUpload'),
|
||
builder: (_) => postFileUpload(
|
||
ClientId: clientId,
|
||
policyTypeId: data['policy_type_id'].toString(),
|
||
ClientPoliyId: data['client_policy_id'].toString(),
|
||
clientBranchId: branchId,
|
||
Token: token,
|
||
TokenType: 'post',
|
||
cardType: data['type'].toString(),
|
||
cardPolicyNo: data['policy_no'].toString(),
|
||
cardInsurer_name:
|
||
data['insurer_short_name'].toString(),
|
||
cardPolicy_name:
|
||
data['policy_name']?.toString() ?? '',
|
||
cardPolicy_ExpDate:
|
||
data['policy_expiry_date'].toString(),
|
||
total_premium: data['total_premium'].toString(),
|
||
allocgType: 'NONEB',
|
||
),
|
||
),
|
||
);
|
||
return;
|
||
}
|
||
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
settings: const RouteSettings(name: 'hrPolicyDetails'),
|
||
builder: (_) => hrPolicyDetails(
|
||
ClientId: clientId,
|
||
policyTypeId: data['policy_type_id'].toString(),
|
||
ClientPoliyId: data['client_policy_id'].toString(),
|
||
clientBranchId: branchId,
|
||
Token: token,
|
||
TokenType: 'post',
|
||
cardType: data['type'].toString(),
|
||
cardPolicyNo: data['policy_no'].toString(),
|
||
cardInsurer_name:
|
||
data['insurer_short_name'].toString(),
|
||
cardPolicy_name: data['policy_name'].toString(),
|
||
cardPolicy_ExpDate:
|
||
data['policy_expiry_date'].toString(),
|
||
total_premium: data['total_premium'].toString(),
|
||
is_ecard_bulk_download_for_employee:
|
||
data['is_ecard_bulk_download_for_employee'],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
// class _PolicyGrid extends StatelessWidget {
|
||
// final List<Map<String, dynamic>> policies;
|
||
// final bool isEnrollment;
|
||
// final Function(String clientPolicyId)? onBulkDownload;
|
||
//
|
||
// const _PolicyGrid({
|
||
// super.key,
|
||
// required this.policies,
|
||
// required this.isEnrollment,
|
||
// this.onBulkDownload,
|
||
// });
|
||
//
|
||
// @override
|
||
// Widget build(BuildContext context) {
|
||
// final tokenService = TokenStorageService();
|
||
//
|
||
// return GridView.builder(
|
||
// physics: const BouncingScrollPhysics(), // ✅ scroll enabled
|
||
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
// crossAxisCount: 4, // desktop
|
||
// crossAxisSpacing: 16,
|
||
// mainAxisSpacing: 16,
|
||
// childAspectRatio: isEnrollment ? 2.8 : 2.4,
|
||
// ),
|
||
// itemCount: policies.length,
|
||
// itemBuilder: (context, index) {
|
||
// final data = policies[index];
|
||
//
|
||
// return isEnrollment
|
||
// ? _EnrollmentPolicyCardNew(
|
||
// data: data,
|
||
// onTap: () async {
|
||
// final String? token = await tokenService.getCurrentToken();
|
||
// final String? enrollmentClientId = await tokenService.readValue('enrollmentClient_id');
|
||
// final String? enrollmentBranchId = await tokenService.readValue('enrollmentEmpClientBranchId');
|
||
//
|
||
// // ✅ SAFETY CHECK
|
||
// if (token == null ||
|
||
// enrollmentClientId == null ||
|
||
// enrollmentBranchId == null) {
|
||
// debugPrint('❌ Missing required data for navigation ${token}');
|
||
// return;
|
||
// }
|
||
//
|
||
// Navigator.push(
|
||
// context,
|
||
// MaterialPageRoute(
|
||
// builder: (context) => hrPolicyDetails(
|
||
// ClientId: enrollmentClientId, // ✅ now String
|
||
// policyTypeId: data['policy_type_id'].toString(),
|
||
// ClientPoliyId: data['client_policy_id'].toString(),
|
||
// clientBranchId: enrollmentBranchId, // ✅ now String
|
||
// Token: token, // ✅ now String
|
||
// TokenType: "pre",
|
||
// cardType: data['type'].toString(),
|
||
// cardPolicyNo: data['policy_no'].toString(),
|
||
// cardInsurer_name: data['insurer_short_name'].toString(),
|
||
// cardPolicy_name: data['policy_name'].toString(),
|
||
// cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
|
||
// total_premium: '',
|
||
// is_ecard_bulk_download_for_employee: 0
|
||
// ),
|
||
// ),
|
||
// );
|
||
// },
|
||
// )
|
||
// : _ActivePolicyCardNew(
|
||
// data: data,
|
||
// onBulkDownload: onBulkDownload,
|
||
// onTap: () async {
|
||
// final String? token = await tokenService.getCurrentToken();
|
||
// final String? empClientId = await tokenService.readValue('empClientId');
|
||
// final String? empBranchId = await tokenService.readValue('empClientBranchId');
|
||
//
|
||
// // ✅ SAFETY CHECK
|
||
// if (token == null ||
|
||
// empClientId == null ||
|
||
// empBranchId == null) {
|
||
// debugPrint('❌ Missing required data for navigation ${token}');
|
||
// return;
|
||
// }
|
||
//
|
||
// logDebug('$token , $empClientId, $empBranchId');
|
||
// logDebug(data);
|
||
// // return;
|
||
//
|
||
// Navigator.push(
|
||
// context,
|
||
// MaterialPageRoute(
|
||
// builder: (context) => hrPolicyDetails(
|
||
// ClientId: empClientId, // <-- from map
|
||
// policyTypeId: data['policy_type_id'].toString(), // <-- from map
|
||
// ClientPoliyId: data['client_policy_id'].toString(),
|
||
// clientBranchId: empBranchId,
|
||
// Token: token,
|
||
// TokenType: 'post',
|
||
// cardType: data['type'].toString(),
|
||
// cardPolicyNo: data['policy_no'].toString(),
|
||
// cardInsurer_name: data['insurer_short_name'].toString(),
|
||
// cardPolicy_name: data['policy_name'].toString(),
|
||
// cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
|
||
// total_premium: data['total_premium'].toString(),
|
||
// is_ecard_bulk_download_for_employee : data['is_ecard_bulk_download_for_employee'],
|
||
// ),
|
||
// ),
|
||
// );
|
||
// },
|
||
// );
|
||
// },
|
||
// );
|
||
// }
|
||
// }
|
||
|
||
class _EnrollmentPolicyCardNew extends StatelessWidget {
|
||
final Map<String, dynamic> data;
|
||
final VoidCallback? onTap;
|
||
|
||
const _EnrollmentPolicyCardNew({
|
||
required this.data,
|
||
this.onTap,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MouseRegion(
|
||
cursor: SystemMouseCursors.click, // 👈 pointer on hover
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(12),
|
||
onTap: onTap, // 👈 card click
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFE9F6FB),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
padding: const EdgeInsets.all(14),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
/// Policy Number
|
||
Tooltip(
|
||
message: '${data['type']} - ${data['policy_no']}',
|
||
waitDuration: const Duration(milliseconds: 300),
|
||
child: Text(
|
||
'${data['type']} - ${data['policy_no']}',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
softWrap: false,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
|
||
// const SizedBox(height: 4),
|
||
//
|
||
// /// Insurer
|
||
// Tooltip(
|
||
// message: data['insurer_name'] ?? '',
|
||
// waitDuration: const Duration(milliseconds: 300),
|
||
// child: Text(
|
||
// data['insurer_name'] ?? '',
|
||
// maxLines: 1,
|
||
// overflow: TextOverflow.ellipsis,
|
||
// softWrap: false,
|
||
// style: GoogleFonts.poppins(
|
||
// fontSize: 11,
|
||
// color: Colors.grey,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
|
||
const SizedBox(height: 10),
|
||
|
||
/// Closes On
|
||
Text(
|
||
'Closes on: ${data['policy_expiry_date'] ?? ''}',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.red,
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 10),
|
||
|
||
/// STATUS ROW
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_StatusPillCount(
|
||
label: 'Draft',
|
||
value: data['membersCountOfDraft'] ?? 0,
|
||
color: Colors.orange,
|
||
),
|
||
_StatusPillCount(
|
||
label: 'Under Process',
|
||
value: data['membersCountOfEnrolled'] ?? 0,
|
||
color: Colors.blue,
|
||
),
|
||
_StatusPillCount(
|
||
label: 'Total',
|
||
value: data['totalMembersCount'] ?? 0,
|
||
color: Colors.green,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ActivePolicyCardNew extends StatelessWidget {
|
||
final Map<String, dynamic> data;
|
||
final VoidCallback? onTap;
|
||
final Function(String clientPolicyId)? onBulkDownload;
|
||
final bool isNonEb;
|
||
|
||
const _ActivePolicyCardNew({
|
||
required this.data,
|
||
this.onTap,
|
||
this.onBulkDownload,
|
||
this.isNonEb = false,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MouseRegion(
|
||
cursor: SystemMouseCursors.click, // 👈 pointer on hover
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(12),
|
||
onTap: onTap, // 👈 card click
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: isNonEb
|
||
? const Color(0xFFFFF3E0) // 🔥 Non-EB
|
||
: const Color(0xFFE9F6FB), // EB
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
padding: const EdgeInsets.all(14),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
/// PREMIUM
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
'Premium - ₹${data['total_premium'] ?? ''}*',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Color(0xFF009195),
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
// 🔥 ICON FLOATING ABOVE CARD
|
||
if (data['is_ecard_bulk_download'] == 1)
|
||
Positioned(
|
||
top: 10,
|
||
right: 10,
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
logDebug(
|
||
'ICON CLICKED ${data['client_policy_id']}');
|
||
onBulkDownload?.call(
|
||
data['client_policy_id'].toString(),
|
||
);
|
||
},
|
||
child: Container(
|
||
padding: const EdgeInsets.all(6),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF009195),
|
||
borderRadius: BorderRadius.circular(6),
|
||
),
|
||
child: const Icon(
|
||
Icons.credit_card,
|
||
color: Colors.white,
|
||
size: 16,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 4),
|
||
|
||
/// POLICY NO + ICON
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
'${data['type']} - ${data['policy_no']}',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 4),
|
||
|
||
/// Insurer
|
||
Tooltip(
|
||
message: data['insurer_name'] ?? '',
|
||
waitDuration: const Duration(milliseconds: 300),
|
||
child: Text(
|
||
data['insurer_name'] ?? '',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
softWrap: false,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 11,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 4),
|
||
|
||
/// DATE RANGE
|
||
Text(
|
||
'${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Color(0xFF8A9B0F),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 10),
|
||
|
||
/// ACTIVE / INACTIVE
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_StatusPillCount(
|
||
label: 'Active',
|
||
value: data['membersCountOfActive'] ?? 0,
|
||
color: Colors.green,
|
||
),
|
||
_StatusPillCount(
|
||
label: 'Inactive',
|
||
value: data['membersCountOfInactive'] ?? 0,
|
||
color: Colors.red,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
)));
|
||
}
|
||
}
|
||
|
||
class _StatusPillCount extends StatelessWidget {
|
||
final String label;
|
||
final int value;
|
||
final Color color;
|
||
|
||
const _StatusPillCount({
|
||
required this.label,
|
||
required this.value,
|
||
required this.color,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 5),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: GoogleFonts.poppins(fontSize: 13, color: color),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
value.toString(),
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.bold,
|
||
color: color,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ActiveExpiredToggle extends StatelessWidget {
|
||
final int selectedIndex;
|
||
final Function(int) onChange;
|
||
|
||
const _ActiveExpiredToggle({
|
||
required this.selectedIndex,
|
||
required this.onChange,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(3),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFE6F4F3),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
_ToggleItem(
|
||
label: 'Active',
|
||
active: selectedIndex == 1,
|
||
onTap: () => onChange(1),
|
||
),
|
||
_ToggleItem(
|
||
label: 'Expired',
|
||
active: selectedIndex == 2,
|
||
onTap: () => onChange(2),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ToggleItem extends StatelessWidget {
|
||
final String label;
|
||
final bool active;
|
||
final VoidCallback onTap;
|
||
|
||
const _ToggleItem({
|
||
required this.label,
|
||
required this.active,
|
||
required this.onTap,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: active ? const Color(0xFF009195) : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
child: Text(
|
||
label,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 11,
|
||
color: active ? Colors.white : Colors.black,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _EmptyBox extends StatelessWidget {
|
||
final String text;
|
||
|
||
const _EmptyBox(this.text);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
height: 180,
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(6),
|
||
border: Border.all(color: Colors.black12),
|
||
),
|
||
child: Center(
|
||
child: Text(text, style: GoogleFonts.poppins(color: Colors.grey)),
|
||
),
|
||
);
|
||
}
|
||
}
|