new hr design

This commit is contained in:
Surendiran 2026-02-05 19:15:46 +05:30
parent 47dab2ff0b
commit b68d89670c
41 changed files with 13115 additions and 7389 deletions

BIN
assets/hrLogin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

4
devtools_options.yaml Normal file
View File

@ -0,0 +1,4 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- shared_preferences: true

View File

@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../customAppBar/base_layout.dart';
import '../customAppBar/customAppBar.dart';
import '../customAppBar/customFooter.dart';
import '../customAppBar/toastHelper.dart';
import '../service/api_service.dart';
import '../service/session/web_session.dart';
import '../service/token_storage_service.dart';
import 'branch_card_widget.dart';
@ -18,12 +19,14 @@ class BranchSelectionPage extends StatefulWidget {
class _BranchSelectionPageState extends State<BranchSelectionPage> {
List<Map<String, dynamic>> branches = [];
late ApiService apiService;
int? selectedIndex;
final tokenStorage = TokenStorageService();
@override
void initState() {
super.initState();
apiService = ApiService(context);
_loadBranches();
}
@ -42,7 +45,7 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
Future<void> _handleNext() async {
if (selectedIndex == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
const SnackBar(
content: Text('Please select a branch'),
backgroundColor: Colors.orange,
),
@ -52,110 +55,72 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
final selectedBranch = branches[selectedIndex!];
// Save selected branch and decode token
// Save selected branch & decode token securely
await tokenStorage.saveSelectedBranch(selectedBranch);
// Debug: Print decoded token
final decodedToken = tokenStorage.getDecodedToken();
print('Selected Branch: $selectedBranch');
print('Decoded Token: $decodedToken');
final token = tokenStorage.getCurrentToken();
// Navigate to home
if (mounted) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
//Post Token Decode Details
final empClientBranchId = decodedToken?['post_branch_id'];
await prefs.setString('empClientBranchId', empClientBranchId);
final empPrimaryId = decodedToken?['post_hr_id'];
await prefs.setString('empPrimaryId', empPrimaryId);
final empClientId = decodedToken?['post_client_id'];
await prefs.setString('empClientId', empClientId);
final empHrId = decodedToken?['post_hr_id'];
await prefs.setString('empHrId', empHrId);
dynamic allowedModules = decodedToken?['allowed_modules'];
// If it's a String, decode it again
if (allowedModules is String) {
allowedModules = jsonDecode(allowedModules);
}
final empAllowedModules = allowedModules['post'];
await prefs.setString(
'empAllowed_modules', jsonEncode(empAllowedModules));
// final empAllowed_modules = decodedToken?['allowed_modules'][0]['post'];
// await prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules));
//Pre Token Decode Details
final enrollmentEmpClientBranchId = decodedToken?['pre_branch_id'];
await prefs.setString(
'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
final enrollmentEmpPrimaryId = decodedToken?['pre_hr_id'];
await prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
final enrollmentClient_id = decodedToken?['pre_client_id'];
await prefs.setString('enrollmentClient_id', enrollmentClient_id);
final enrollmentHrId = decodedToken?['pre_hr_id'];
await prefs.setString('enrollmentHrId', enrollmentHrId);
final enrollmentAllowedModules = allowedModules['pre'];
await prefs.setString(
'enrollmentAllowed_modules', jsonEncode(enrollmentAllowedModules));
// final enrollmentAllowed_modules = decodedToken?['allowed_modules'][1]['pre'];
// await prefs.setString('enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
final token = await tokenStorage.getCurrentToken();
print('tokenbranch - $token');
await prefs.setString('token', token!);
ToastHelper.showSuccessToast(context, 'Successfully Login');
Navigator.pushReplacementNamed(context, 'hrDashboard');
if (decodedToken == null || token == null) {
ToastHelper.showErrorToast(context, 'Invalid token data');
return;
}
// final branch = branches[selectedIndex!];
// // =====================
// // POST (HR)
// // =====================
// WebSession.postClientId = branch['client_id']?.toString();
// WebSession.postBranchId =
// branch['post_branch_id']?.toString() ??
// branch['client_branch_id']?.toString();
// WebSession.postHrId = branch['id']?.toString();
// WebSession.postModules =
// branch['allowed_modules']?['post'] ?? [];
//
// // =====================
// // PRE (Enrollment)
// // =====================
// WebSession.preClientId = branch['client_id']?.toString();
// WebSession.preBranchId =
// branch['pre_branch_id']?.toString() ??
// branch['client_branch_id']?.toString();
// WebSession.preHrId = branch['id']?.toString();
// WebSession.preModules =
// branch['allowed_modules']?['pre'] ?? [];
//
// 🔐 Save decoded values securely
await tokenStorage.saveDecodedSessionData(decodedToken, token);
// ToastHelper.showSuccessToast(context, 'Successfully Login');
//
// if (!mounted) return;
// Navigator.pushReplacementNamed(context, 'hrDashboard');
if (!mounted) return;
Navigator.pushReplacementNamed(context, 'policies');
}
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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
@ -189,8 +154,7 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
childAspectRatio = 4;
}
return Scaffold(
backgroundColor: Color(0xFFEFF3F6),
appBar: CustomAppBar(),
backgroundColor: Color(0xFFF5F7F7),
body: Column(
children: [
Expanded(
@ -232,36 +196,36 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
height: gridHeight, // Shows approximately 3 rows
child: branches.isEmpty
? Center(
child: Text(
'No branches available',
style: TextStyle(
fontSize: 16, color: Colors.grey),
),
)
child: Text(
'No branches available',
style: TextStyle(
fontSize: 16, color: Colors.grey),
),
)
: GridView.builder(
// Enable scrolling if content exceeds height
physics: ClampingScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
childAspectRatio: childAspectRatio,
crossAxisSpacing: 15,
mainAxisSpacing: 15,
),
itemCount: branches.length,
itemBuilder: (context, index) {
final branch = branches[index];
return BranchCard(
clientName: branch['client_name'] ??
'Unknown Client',
branchName: branch['branch_name'] ??
'Unknown Branch',
isSelected: selectedIndex == index,
onTap: () => _selectBranch(index),
);
},
),
// Enable scrolling if content exceeds height
physics: ClampingScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
childAspectRatio: childAspectRatio,
crossAxisSpacing: 15,
mainAxisSpacing: 15,
),
itemCount: branches.length,
itemBuilder: (context, index) {
final branch = branches[index];
return BranchCard(
clientName: branch['client_name'] ??
'Unknown Client',
branchName: branch['branch_name'] ??
'Unknown Branch',
isSelected: selectedIndex == index,
onTap: () => _selectBranch(index),
);
},
),
),
SizedBox(height: 24),
Center(
@ -294,10 +258,6 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
),
),
),
Container(
width: double.infinity,
child: CustomFooter(),
),
],
),
);

View File

@ -1,656 +0,0 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhancepolicy/service/api_service.dart';
class ClaimHistoryPopup extends StatefulWidget {
final String ticket_id;
final String empName;
final String empCode;
final String policyType;
final String clientPolicyNo;
final String claimAmount;
final String claimNo;
final String postToken;
const ClaimHistoryPopup({
Key? key,
required this.ticket_id,
required this.empName,
required this.empCode,
required this.policyType,
required this.clientPolicyNo,
required this.claimAmount,
required this.claimNo,
required this.postToken,
}) : super(key: key);
@override
State<ClaimHistoryPopup> createState() => _ClaimHistoryPopupState();
}
class _ClaimHistoryPopupState extends State<ClaimHistoryPopup> {
late ApiService apiService;
List<Map<String, dynamic>> getClaimsHistoryList = [];
List<String> stepKeys = [];
late Map<String, dynamic> stepMap;
int _index = 4;
bool isLoading = false;
@override
void initState() {
super.initState();
apiService = ApiService(context);
print(widget.postToken);
print(widget.claimAmount);
print(widget.claimNo);
print(widget.clientPolicyNo);
print(widget.empCode);
print(widget.policyType);
print(widget.ticket_id);
getClaimsHistoryDetails();
}
@override
void dispose() {
super.dispose();
}
Future<void> getClaimsHistoryDetails() async {
setState(() {
isLoading = true;
});
try {
print('10');
// final ticketID = widget.ticket_id;
// if (ticketID != '' || ticketID != null) {
// return;
// }
final response = await apiService.getClaimsHistoryToApi(
widget.ticket_id, widget.postToken);
if (response['status'] == 'success') {
setState(() {
isLoading = false;
});
setState(() {
getClaimsHistoryList = [
Map<String, dynamic>.from(response['data']['ticket_data'])
];
stepMap = getClaimsHistoryList[0];
stepKeys = stepMap.keys.toList();
print('Claims History List $getClaimsHistoryList');
// originalData = getCDPolicies;
// filteredData = List.from(originalData);
// print('filteredData');
// print(filteredData);
});
} else {
setState(() {
isLoading = false;
});
// ToastHelper.showWarningToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response['code']}');
}
} catch (e) {
setState(() {
isLoading = false;
});
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
// if (getClaimsHistoryList.isEmpty) {
// return SizedBox(
// height: 50,
// child: Center(child: Text('No available Claims')),
// );
// }
return Container(
constraints: BoxConstraints(maxWidth: 800, maxHeight: 800),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Claim History',
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Color(0xFF101010),
),
),
),
),
MouseRegion(
cursor: SystemMouseCursors.click, // Show pointer cursor
child: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Color(0xFFBCBCBC)),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(
Icons.close,
size: 25,
color: Color(0xFFBCBCBC),
),
),
),
)
],
),
SizedBox(height: 10),
Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: BoxDecoration(
color: Color(0xFFFFFFFF), // White background
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0xFFEBEBEB), // Shadow color
blurRadius: 14, // How soft the shadow is
spreadRadius: 2, // How much it spreads
offset: Offset(0, 1), // X and Y offset
),
],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: _buildKeyValue('Name',
'${widget.empName ?? ''} (${widget.empCode ?? ''})'),
),
SizedBox(width: 16),
Expanded(
child: _buildKeyValue('Policy Name',
'${widget.policyType ?? ''} - ${widget.clientPolicyNo ?? ''}')),
],
),
SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildKeyValue(
'Claim Amount', '${widget.claimAmount ?? ''}'),
),
SizedBox(width: 16),
Expanded(
child: _buildKeyValue(
'Claim Number', widget.claimNo ?? ''),
),
],
),
],
),
),
SizedBox(height: 10),
// Container(
// padding: const EdgeInsets.all(16),
// child: Stepper(
// currentStep: _index,
// onStepCancel: () {
// if (_index > 0) {
// setState(() {
// _index -= 1;
// });
// }
// },
// onStepContinue: () {
// if (_index < 4) {
// setState(() {
// _index += 1;
// });
// }
// },
// onStepTapped: (int index) {
// setState(() {
// _index = index;
// });
// },
// steps: <Step>[
// Step(
// title: Text('Step 1: PAYMENT INITIATED'),
// content: Text('Pay Initiate Date: 05-05-2025'),
// isActive: true,
// state: StepState.complete,
// ),
// Step(
// title: Text('Step 2: INFORMATION REQUIRED'),
// content: Text('Raised Date: 05-05-2025'),
// isActive: true,
// state: StepState.complete,
// ),
// Step(
// title: Text('Step 3: CLAIM NO. UPDATION'),
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text('Claim Number: 4315440'),
// Text('Registration Date: 05-05-2025'),
// ],
// ),
// isActive: true,
// state: StepState.complete,
// ),
// Step(
// title: Text('Step 4: QUERY DOCUMENT REQUIRED'),
// content: Text('Query Received Date: 05-05-2025'),
// isActive: true,
// state: StepState.complete,
// ),
// Step(
// title: Text('Step 5: APPROVED'),
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text('Approved Amount: 4315440'),
// Text('Approved Date: 05-05-2025'),
// Text('Approved Letter: Lorem ipsum...'),
// Text('Description: Lorem ipsum...'),
// ],
// ),
// isActive: true,
// state: StepState.complete,
// ),
// ],
// ),
// )
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: List.generate(5, (index) {
// return _buildStep(
// stepNumber: index + 1,
// title: _getStepTitle(index),
// content: _getStepContent(index),
// isLast: index == 4,
// );
// }),
// )
isLoading
? Container(
// color: Color(0x98FFFCE5), // 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
),
)
: Container(
child: getClaimsHistoryList.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(stepKeys.length, (index) {
String stepTitleKey = stepKeys[index];
Map<String, dynamic> stepData =
stepMap[stepTitleKey];
print('stepTitleKey');
print(stepTitleKey);
print('stepData');
print(stepData);
Widget content = _getStepContentFromApi(stepData);
print('check');
print(context);
// // 🟡 Skip step if there's no valid content (e.g., ID NOT GENERATED case)
// if ((content as Column).children.isEmpty) {
// print(
// "Skipping step $stepTitleKey due to no valid content");
// content = Text("No content available",
// style: TextStyle(color: Colors.grey));
// // or `return Container()`
// }
// if (content == null) {
// print(
// "Skipping step: $stepTitleKey due to no valid content");
// return const SizedBox(); // Completely skip step
// }
return _buildStep(
stepNumber: index + 1,
title: _getStepTitleFromApi(
stepTitleKey, stepData),
content: content,
// content: _getStepContentFromApi(stepData),
isLast: index == stepKeys.length - 1,
);
}),
)
: Container(
height: MediaQuery.of(context).size.height * 0.4,
// color: Colors.red,
child: Center(
child: Column(
children: [
Image.asset(
'assets/claimsData.png', // Replace 'default_image.png' with your default image asset path
width: 350,
height: 350,
fit: BoxFit.cover,
),
const Text(
'No Available Claims',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 15),
),
],
)),
),
)
],
),
),
);
}
Widget _buildStep({
required int stepNumber,
// required String title,
required Widget title,
required Widget content,
bool isLast = false,
}) {
print(title);
print("contentss - $content");
final noContent;
if ((content as Column).children.isEmpty) {
noContent = 0;
} else {
noContent = 1;
}
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Left Column with circle + line
Column(
children: [
// Add top spacing before circle
SizedBox(height: stepNumber == 1 ? 0 : 4),
// Step number circle
Container(
height: 28,
width: 28,
decoration: BoxDecoration(
color: Color(0xFF00A5A8),
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
'$stepNumber',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
// Dotted line below circle (except for last step)
if (!isLast)
Container(
height: noContent == 1 ? 70 : 25, // increase to extend line
width: 2,
margin: EdgeInsets.only(top: 4, bottom: 4),
child: CustomPaint(
painter: DottedLinePainter(),
),
),
],
),
SizedBox(width: 12),
// Right Side: Step title and content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title,
if (noContent == 1) SizedBox(height: noContent == 0 ? 0 : 8),
if (noContent == 1)
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFFF7F7F7),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Color(0xFFE0E0E0)),
),
child: content,
),
if (noContent == 1) SizedBox(height: isLast ? 0 : 16),
],
),
),
],
);
}
// String _getStepTitleFromApi(String status, Map<String, dynamic> data) {
// final modifiedBy = data['modified_by'] ?? '';
// final modifiedAt = data['modified_at'] ?? '';
// return '$status ($modifiedBy $modifiedAt)';
// }
Widget _getStepTitleFromApi(String status, Map<String, dynamic> data) {
final modifiedBy = data['modified_by'] ?? '';
final modifiedAt = data['modified_at'] ?? '';
final symbol = (data['modified_by'] != null && data['modified_by'] != '') ? ' - ' : '';
return RichText(
text: TextSpan(
children: [
TextSpan(
text: status,
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Color(0xFF212120), // Status color
),
),
TextSpan(
text: ' ($modifiedBy$symbol$modifiedAt)',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF565656), // Subtitle color
),
),
],
),
);
}
Widget _getStepContentFromApi(Map<String, dynamic> data) {
List<Widget> rows = [];
print("data - $data");
// bool hasDisplayFields(Map<String, dynamic> map) {
// for (var entry in map.entries) {
// if (entry.value is Map<String, dynamic>) {
// final innerMap = entry.value as Map<String, dynamic>;
// if (innerMap.containsKey('display_name') ||
// innerMap.containsKey('display_value')) {
// print("display fields found");
// return true;
// }
// }
// }
// return false;
// }
//
// if (!hasDisplayFields(data)) {
// print("❌ Skipping because no display fields found");
// return SizedBox.shrink(); // or return an empty Container/Spacer if needed
// }
data.forEach((key, value) {
// Skip metadata
if (key == 'modified_by' || key == 'modified_at') return;
if (value is Map<String, dynamic>) {
final displayName = value['display_name'];
final displayValue = value['display_value'];
if (displayName != null && displayValue != null) {
print("✅ displayName - $displayName, displayValue - $displayValue");
rows.add(_buildHistoryListData(displayName, displayValue));
rows.add(SizedBox(height: 6));
}
}
});
// data.forEach((key, value) {
// // Skip metadata fields
// if (key == 'modified_by' || key == 'modified_at') return;
// print("ContentKey - $key");
// print("Content - $value");
// String displayName = value['display_name'] ?? key;
// String displayValue = value['display_value'] ?? 'N/A';
//
// print("displayName - $displayName");
// print("displayValue - $displayValue");
// if (displayName == 'N/A' || displayValue == 'N/A') return;
//
// rows.add(_buildHistoryListData(displayName, displayValue));
// rows.add(SizedBox(height: 6));
// });
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: rows,
);
}
Widget _buildKeyValue(String title, String value) {
final displayValue =
(value == null || value.trim().isEmpty) ? 'N/A' : value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: GoogleFonts.poppins(
color: Color(0xFF747474),
fontWeight: FontWeight.w400,
fontSize: 16,
),
),
SizedBox(height: 4),
Text(
displayValue,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontWeight: FontWeight.w500,
fontSize: 16,
),
),
],
);
}
Widget _buildHistoryListData(String title, String value) {
print("_buildHistoryListData");
final displayValue =
(value == null || value.trim().isEmpty) ? 'N/A' : value;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6), // optional spacing
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Title - Align to center left
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: GoogleFonts.poppins(
color: const Color(0xFF747474),
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
),
),
// Value - Align to center right
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Text(
displayValue,
style: GoogleFonts.poppins(
color: const Color(0xFF000000),
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
),
),
],
),
);
}
}
class DottedLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
const dashHeight = 2.0;
const dashSpace = 3.0;
double startY = 0;
final paint = Paint()
..color = Colors.grey.shade400
..strokeWidth = 1;
while (startY < size.height) {
canvas.drawLine(
Offset(0, startY),
Offset(0, startY + dashHeight),
paint,
);
startY += dashHeight + dashSpace;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'top_app_bar.dart';
import 'side_bar.dart';
class BaseLayout extends StatelessWidget {
final Widget child;
const BaseLayout({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const NhanceTopBar(),
body: Row(
children: [
const NhanceSideBar(),
Expanded(
child: Container(
color: const Color(0xFFF5F7F7),
padding: const EdgeInsets.all(16),
child: child,
),
),
],
),
);
}
}

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:adaptive_navbar/adaptive_navbar.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/responsive.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:nhancepolicy/responsive.dart';
import '../service/api_service.dart';
import '../service/token_storage_service.dart';
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
@override
@ -18,6 +19,9 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
class _CustomAppBarState extends State<CustomAppBar> {
bool showBackToHR = false;
late ApiService apiService;
final tokenService = TokenStorageService();
// 🔐 Secure storage instance
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage();
// bool isLoading = true; // Add a loading state
// bool hideInactiveStatus = true;
@ -38,6 +42,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
// Navigator.pushNamed(context, 'hrLogin');
}
@override
Widget build(BuildContext context) {
final sw = MediaQuery.of(context).size.width;
@ -89,24 +94,14 @@ class _CustomAppBarState extends State<CustomAppBar> {
NavBarItem(
text: "Change Branch",
onTap: () async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('selected_branch');
await prefs.remove('decoded_token');
await prefs.remove('clientLogo');
await prefs.remove('clientName');
await prefs.remove('empAllowed_modules');
await prefs.remove('empClientBranchId');
await prefs.remove('empClientId');
await prefs.remove('empEmail');
await prefs.remove('empHrId');
await prefs.remove('empPrimaryId');
await prefs.remove('enrollmentAllowed_modules');
await prefs.remove('enrollmentClient_id');
await prefs.remove('enrollmentEmpClientBranchId');
await prefs.remove('enrollmentEmpPrimaryId');
await prefs.remove('enrollmentHrId');
await prefs.remove('token');
Navigator.pushNamed(context, 'branchSelection');
await tokenService.clearBranchSession();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'branchSelection',
(route) => false,
);
},
),
const SizedBox(width: 8),

View File

@ -0,0 +1,306 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'package:nhancepolicy/service/svg_service.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
class NhanceSideBar extends StatefulWidget {
const NhanceSideBar({super.key});
@override
State<NhanceSideBar> createState() => _NhanceSideBarState();
}
class _NhanceSideBarState extends State<NhanceSideBar> {
String? activeRoute;
late ApiService apiService;
// bool isLoading = true; // Add a loading state
// bool hideInactiveStatus = true;
final tokenService = TokenStorageService();
// dynamic enrollmentModules = [];
// dynamic postModules = [];
List<Map<String, dynamic>> sideMenuItems = [];
@override
void initState() {
super.initState();
apiService = ApiService(context);
_buildSideMenu();
// _checkTokens();
}
Future<void> _buildSideMenu() async {
final enrollmentRaw =
await tokenService.readValue('enrollmentAllowed_modules'); // "[1]"
final postRaw =
await tokenService.readValue('empAllowed_modules'); // "[2,3,4]"
print('enrollmentRaw $enrollmentRaw');
print('postRaw $postRaw');
// Decode safely
final List<int> enrollmentModules =
enrollmentRaw != null && enrollmentRaw.isNotEmpty
? List<int>.from(jsonDecode(enrollmentRaw))
: [];
final List<int> postModules =
postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: [];
print('enrollmentModules $enrollmentModules');
print('postModules $postModules');
final List<Map<String, dynamic>> items = [];
// POLICIES (1 OR 2)
if (enrollmentModules.contains(1) || postModules.contains(2)) {
items.add({
'route': 'policies',
'label': 'Policies',
'icon': 'policies',
});
}
// CD
if (postModules.contains(3)) {
items.add({
'route': 'CdPoliciesList',
'label': 'CD',
'icon': 'cd',
});
}
// CLAIMS
if (postModules.contains(4)) {
items.add({
'route': 'ClaimsPolicies',
'label': 'Claims',
'icon': 'claims',
});
}
setState(() {
sideMenuItems = items;
});
}
Future<void> logout(BuildContext context) async {
// final prefs = await SharedPreferences.getInstance();
// final String? hrtoken = prefs.getString('_postToken');
// final String? token = prefs.getString('enrollToken');
// prefs.clear();
print('LocalStorage Cleared');
apiService.logout();
// Navigator.pushNamed(context, 'hrLogin');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
activeRoute = ModalRoute.of(context)?.settings.name;
}
void _navigate(String routeName) {
if (activeRoute == routeName) return;
setState(() {
activeRoute = routeName;
});
Navigator.pushReplacementNamed(context, routeName);
}
@override
Widget build(BuildContext context) {
return Container(
width: 70,
color: const Color(0xFF009E9E),
child: Column(
children: [
// const SizedBox(height: 12),
//
// _SideItem(
// // icon: Icons.policy,
// icon: SvgPicture.string(
// SvgService.getSvg('policies'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Policies",
// isActive: activeRoute == 'oldPolicy', // or correct policy route
// onTap: () => _navigate('oldPolicy'),
// ),
//
// _SideItem(
// // icon: Icons.credit_card,
// icon: SvgPicture.string(
// SvgService.getSvg('cd'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "CD",
// isActive: activeRoute == 'cdTransactionDetails',
// onTap: () => _navigate('cdTransactionDetails'),
// ),
//
// _SideItem(
// // icon: Icons.assignment,
// icon: SvgPicture.string(
// SvgService.getSvg('claims'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Claims",
// isActive: activeRoute == 'claims', // claims tab inside dashboard
// onTap: () => _navigate('claims'),
// ),
//
// const Spacer(),
//
// _SideItem(
// // icon: Icons.logout,
// icon: SvgPicture.string(
// SvgService.getSvg('logout'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Logout",
// onTap: () {
// logout(context);
// },
// ),
...sideMenuItems.map((item) {
return _SideItem(
icon: SvgPicture.string(
SvgService.getSvg(item['icon']),
width: 35,
height: 35,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
label: item['label'],
isActive: activeRoute == item['route'],
onTap: () => _navigate(item['route']),
);
}).toList(),
if(activeRoute == 'CdPoliciesList' || activeRoute == 'ClaimsPolicies' || activeRoute == 'policies' || activeRoute == 'hrDashboard')
_SideItem(
// icon: Icons.dashboard,
icon: SvgPicture.string(
SvgService.getSvg('dashboard'),
width: 35,
height: 35,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
label: "Insights",
isActive: activeRoute == 'hrDashboard',
onTap: () => _navigate('hrDashboard'),
),
const Spacer(),
_SideItem(
icon: SvgPicture.string(
SvgService.getSvg('logout'),
width: 35,
height: 35,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
label: "Logout",
onTap: () => logout(context),
),
// const SizedBox(height: 10),
],
),
);
}
}
class _SideItem extends StatelessWidget {
// final IconData icon;
final Widget icon; // 👈 changed
final String label;
final bool isActive;
final VoidCallback? onTap;
const _SideItem({
required this.icon,
required this.label,
this.isActive = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isActive
? const Color(0xFF065D61) // ACTIVE like your screenshot
: Colors.transparent,
),
child: Column(
children: [
// 👇 SVG or Icon widget
icon,
// Icon(icon, color: Colors.white, size: 22),
const SizedBox(height: 6),
Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}

View File

@ -9,7 +9,12 @@ class ToastHelper {
type: ToastificationType.success,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(message),
title: Text(
message,
maxLines: 3, // allow wrapping
overflow: TextOverflow.visible,
softWrap: true,
),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),

View File

@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import '../service/token_storage_service.dart';
class NhanceTopBar extends StatefulWidget implements PreferredSizeWidget {
const NhanceTopBar({super.key});
@override
Size get preferredSize => const Size.fromHeight(64);
@override
State<NhanceTopBar> createState() => _NhanceTopBarState();
}
class _NhanceTopBarState extends State<NhanceTopBar> {
final tokenStorage = TokenStorageService();
List<Map<String, dynamic>> branches = [];
Map<String, dynamic>? selectedBranch;
@override
void initState() {
super.initState();
_loadBranches();
}
void _loadBranches() {
branches = tokenStorage.getCombinedBranches();
selectedBranch = tokenStorage.getSelectedBranch();
setState(() {});
}
Future<void> _onBranchSelected(Map<String, dynamic> branch) async {
final tokenStorage = TokenStorageService();
await tokenStorage.resetSessionAndSwitchBranch(branch);
if (!mounted) return;
// 🔄 Refresh CURRENT PAGE only
final route = ModalRoute.of(context)?.settings.name ?? 'hrDashboard';
Navigator.pushReplacementNamed(context, route);
}
@override
Widget build(BuildContext context) {
return AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFBFEFEF),
elevation: 0,
title: Row(
children: [
Image.asset('assets/nhance_client_logo.png', height: 36),
const Spacer(),
if (selectedBranch != null)
_BranchPopup(
clientName: selectedBranch!['client_name'],
branchName: selectedBranch!['branch_name'],
branches: branches,
onSelected: _onBranchSelected,
),
],
),
);
}
}
class _BranchPopup extends StatelessWidget {
final String clientName;
final String branchName;
final List<Map<String, dynamic>> branches;
final Function(Map<String, dynamic>) onSelected;
const _BranchPopup({
required this.clientName,
required this.branchName,
required this.branches,
required this.onSelected,
});
@override
Widget build(BuildContext context) {
return PopupMenuButton<Map<String, dynamic>>(
tooltip: '',
offset: const Offset(0, 48),
onSelected: onSelected,
itemBuilder: (context) {
return branches.map((branch) {
return PopupMenuItem<Map<String, dynamic>>(
value: branch,
child: Row(
children: [
Expanded(
child: Text(
branch['client_name'],
style: const TextStyle(fontSize: 13),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 10),
const Icon(Icons.location_on, size: 14, color: Colors.grey),
const SizedBox(width: 4),
Text(
branch['branch_name'],
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
);
}).toList();
},
child: Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
// CLIENT NAME
Text(
clientName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 8),
// 📍 BRANCH NAME (SELECTED)
Row(
children: [
const Icon(
Icons.location_on,
size: 14,
color: Colors.grey,
),
const SizedBox(width: 4),
Text(
branchName,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
const SizedBox(width: 6),
// DROPDOWN ARROW
const Icon(
Icons.keyboard_arrow_down,
size: 20,
color: Colors.orange,
),
],
),
),
);
}
}

View File

@ -129,7 +129,7 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
if (mainStatus != 'success' && postStatus != 'success') {
ToastHelper.showErrorToast(
context,
postEnrollment1['message'] ?? 'User not found',
postEnrollment1['message'],
);
return;
}
@ -430,33 +430,7 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
@override
Widget build(BuildContext context) {
// Retrieve the passed mobile number value
// final String mobileNumber =
// ModalRoute.of(context)!.settings.arguments as String;
Size _size = MediaQuery.of(context).size;
EdgeInsets marginInsets = EdgeInsets.zero;
if (Responsive.isDesktop(context)) {
marginInsets = const EdgeInsets.only(
left: 0,
right: 0,
bottom: 0,
top: 0,
);
} else if (Responsive.isMobile(context)) {
marginInsets = const EdgeInsets.only(
left: 25, // Example value for mobile
right: 25, // Example value for mobile
bottom: 0, // Example value for mobile
top: 0, // Example value for mobile
);
} else if (Responsive.isTablet(context)) {
marginInsets = const EdgeInsets.only(
left: 25, // Example value for mobile
right: 25, // Example value for mobile
bottom: 0, // Example value for mobile
top: 0, // Example value for mobile
);
}
final Size _size = MediaQuery.of(context).size;
final defaultPinTheme = PinTheme(
width: 56,
height: 56,
@ -482,495 +456,263 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
),
);
return WillPopScope(
onWillPop: () async {
Navigator.pushReplacementNamed(context, 'login');
return false;
},
child: Scaffold(
body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container(
height: _size.height,
color: Colors.white,
child: Stack(
children: [
// Visibility(
// visible: _size.width <= 1100,
// child: ClipRRect(
// borderRadius: BorderRadius.only(
// bottomLeft: Radius.circular(30),
// bottomRight: Radius.circular(30),
// ),
// child: Container(
// height: _size.height / 3,
// width: double.infinity,
// color: Color(0xFFFFFCE5),
// child: Stack(
// children: [
// Column(
// children: [
// SizedBox(
// height: _size.height /
// 6.4), // Adjust the spacing between the rows
// Row(
// mainAxisAlignment: MainAxisAlignment
// .center, // Align to the center
// children: [
// Expanded(
// flex: Responsive.isDesktop(context)
// ? 10
// : 12,
// child: Align(
// alignment: Responsive.isDesktop(context)
// ? Alignment.centerLeft
// : Alignment.bottomCenter,
// child: Image.asset(
// 'assets/nhance_app_logo.png',
// width: 150,
// height: 150,
// ),
// ),
// ),
// if (!Responsive.isMobile(context) &&
// !Responsive.isTablet(context))
// Expanded(
// flex: 2,
// child: MouseRegion(
// cursor: SystemMouseCursors.click,
// child: GestureDetector(
// onTap: () {
// // Add your navigation logic here
// // For example, you can use Navigator.push to navigate to another page
// Navigator.pushNamed(
// context, 'hrLogin');
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment
// .end, // Align to the end (right)
// children: [
// Text(
// 'HR Login',
// style: GoogleFonts.poppins(
// color: Color(
// 0xFF000000), // Text color
// // Add other text styles as needed
// ),
// ),
// SizedBox(width: 5),
// Icon(
// Icons
// .east, // Icon for customer login
// color: Colors
// .black, // Adjust color as needed
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ],
// ),
// ),
// ),
// ),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
Row(
children: [
if (_size.width > 1100)
Expanded(
flex: _size.width < 1100 ? 6 : 12,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints constraints) {
if (constraints.maxWidth > 600) {
return Image.asset(
'assets/hrLogin.jpg',
height: _size.height,
fit: BoxFit.cover,
);
} else {
return SizedBox();
}
},
),
),
Expanded(
flex: _size.width < 1100 ? 6 : 12,
child: Container(
margin: _size.width > 1100
? EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(left: 0, right: 0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
if (!Responsive.isMobile(context) &&
!Responsive.isTablet(context))
Row(
children: [
Expanded(
flex: 10,
child: Align(
alignment: Responsive
.isDesktop(context)
? Alignment.center
: Alignment
.bottomCenter, // Align to the start
child: _size.width <= 1100
? Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 150,
)
: _size.width > 1100
? Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 150,
)
: Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 150,
),
)),
],
),
SizedBox(height: 10),
// SizedBox(
// height: Responsive.isDesktop(context)
// ? _size.height * 0.1
// : 10,
// ),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
"Welcome to Nhance",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(height: 10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
"Please enter the one-time usage code sent to your ",
style: TextStyle(
fontSize: 12,
height: 1.5,
color: Color(0xFF000000)),
children: [
TextSpan(
text: widget.type == 'mobile' ? 'mobile number ${widget.value}' : 'Email Id ${widget.value}',
),
TextSpan(
text: widget.type == 'mobile' ? ' (Change Mobile)' : ' (Change Email)',
style: TextStyle(
fontSize: 12,
color: Color(
0xFFE26728)), // Change color as desired
recognizer:
TapGestureRecognizer()
..onTap = () {
// Navigate to the page where the user can change the phone number
Navigator.pushNamed(
context,
'hrLogin');
},
),
],
),
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Pinput(
length: 6,
// defaultPinTheme: defaultPinTheme,
// focusedPinTheme: focusedPinTheme,
// submittedPinTheme: submittedPinTheme,
showCursor: true,
controller: _otpController,
),
),
SizedBox(height: 10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment
.end, // Align text to the right
children: [
_isTimerRunning
? Text(
"Resend OTP in $_secondsRemaining seconds",
style:
GoogleFonts.poppins(
color:
Colors.black),
)
: InkWell(
onTap: () {
_resendOTP();
},
child: Text(
"Resend OTP",
style:
GoogleFonts.poppins(
color: Colors
.blue),
),
),
],
),
),
SizedBox(height: 10),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10),
),
),
onPressed: () {
if (_formKey.currentState!
.validate()) {
_formKey.currentState!
.save(); // Save form fields before calling verifyOTP
verifyOTP(
_otpController.text);
}
},
child: Text(
"Submit",
style: GoogleFonts.poppins(
color: Color(0xFFFFFFFF)),
),
),
),
),
// _size.width > 1100
// ? Container(
// margin: EdgeInsets.symmetric(
// horizontal: 150),
// child: Column(
// children: [
// SizedBox(height: 20),
// Text(
// "Benefits of Login",
// style:
// GoogleFonts.poppins(
// fontSize: 20,
// fontWeight:
// FontWeight.bold,
// ),
// ),
// SizedBox(height: 15),
// ],
// ))
// : SizedBox(),
// _size.width > 1100
// ? Container(
// margin: EdgeInsets.symmetric(
// horizontal: 150),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// Expanded(
// flex: 6,
// child: Container(
// padding: EdgeInsets
// .symmetric(
// vertical: 8),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment
// .center,
// children: [
// Expanded(
// child: Container(
// padding: EdgeInsets
// .symmetric(
// vertical:
// 12),
// decoration:
// BoxDecoration(
// border:
// Border(
// right:
// BorderSide(
// width: 1,
// color: Colors
// .black,
// ),
// ),
// ),
// child: Column(
// children: [
// Icon(
// Icons
// .policy,
// color: Color(
// 0xFFE26728)),
// SizedBox(
// height:
// 10),
// Text(
// "View Policy",
// style: GoogleFonts
// .poppins()),
// ],
// ),
// ),
// ),
// Expanded(
// child: Container(
// padding: EdgeInsets
// .symmetric(
// vertical:
// 12),
// child: Column(
// children: [
// Icon(
// Icons
// .edit,
// color: Color(
// 0xFFE26728)),
// SizedBox(
// height:
// 10),
// Text(
// "Manage Claims",
// style: GoogleFonts
// .poppins()),
// ],
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// )
// : SizedBox(
// height: Responsive.isDesktop(
// context)
// ? _size.height * 0.1
// : _size.height * 0.2,
// ),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.3
: _size.height * 0.2,
),
// SizedBox(
// height: _size.height * 0.1,
// ),
Container(
alignment: Alignment.bottomCenter,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: <TextSpan>[
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
],
),
),
),
],
),
),
),
],
),
],
return Scaffold(
body: Container(
width: double.infinity,
height: _size.height,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Color(0xFF00B6AC),
Color(0xFF83E0DE),
Color(0xFF01B4A8),
],
),
),
child: Center(
child: Container(
width: double.infinity, // fixed web width
height: _size.height, // fixed web height (IMPORTANT)
margin: const EdgeInsets.all(60),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(40)),
),
child: Row(
children: [
// ================= LEFT IMAGE =================
Expanded(
flex: 5,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(40),
bottomLeft: Radius.circular(40),
),
child: Image.asset(
'assets/hrLogin.png',
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
),
),
),
],
)),
)));
// ================= RIGHT FORM =================
Expanded(
flex: 7,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 300,
height: 100,
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
"Welcome to Nhance",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
"Please enter the one-time usage code sent to your ",
style: TextStyle(
fontSize: 12,
height: 1.5,
color: Color(0xFF000000)),
children: [
TextSpan(
text: widget.type == 'mobile' ? 'mobile number ${widget.value}' : 'Email Id ${widget.value}',
),
TextSpan(
text: widget.type == 'mobile' ? ' (Change Mobile)' : ' (Change Email)',
style: TextStyle(
fontSize: 12,
color: Color(
0xFFE26728)), // Change color as desired
recognizer:
TapGestureRecognizer()
..onTap = () {
// Navigate to the page where the user can change the phone number
Navigator.pushNamed(
context,
'hrLogin');
},
),
],
),
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Pinput(
length: 6,
// defaultPinTheme: defaultPinTheme,
// focusedPinTheme: focusedPinTheme,
// submittedPinTheme: submittedPinTheme,
showCursor: true,
controller: _otpController,
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment
.end, // Align text to the right
children: [
_isTimerRunning
? Text(
"Resend OTP in $_secondsRemaining seconds",
style:
GoogleFonts.poppins(
color:
Colors.black),
)
: InkWell(
onTap: () {
_resendOTP();
},
child: Text(
"Resend OTP",
style:
GoogleFonts.poppins(
color: Colors
.blue),
),
),
],
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10),
),
),
onPressed: () {
if (_formKey.currentState!
.validate()) {
_formKey.currentState!
.save(); // Save form fields before calling verifyOTP
verifyOTP(
_otpController.text);
}
},
child: Text(
"Submit",
style: GoogleFonts.poppins(
color: Color(0xFFFFFFFF)),
),
),
),
),
SizedBox(height: 15),
Container(
alignment: Alignment.bottomCenter,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: <TextSpan>[
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
],
),
),
),
],
),
),
),
),
],
),
),
)
)
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,979 +0,0 @@
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/service/hrDashboardTabs/claims.dart';
import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'config/environment.dart';
import 'customAppBar/customAppBar.dart';
import 'customAppBar/customFooter.dart';
import 'customAppBar/toastHelper.dart';
import 'package:universal_html/html.dart' as html;
import 'package:intl/intl.dart';
class hrDashboard extends StatefulWidget {
final int selectedIndex;
late final int isHrcode;
final String empCodeFromHrPolicy;
hrDashboard({
Key? key,
required this.selectedIndex,
required this.isHrcode,
required this.empCodeFromHrPolicy,
}) : super(key: key);
@override
State<hrDashboard> createState() => _hrDashboardState();
}
class _hrDashboardState extends State<hrDashboard>
with SingleTickerProviderStateMixin {
late ApiService apiService;
late TabController _tabController;
bool isLoading = false;
int isHrcode = 0;
String? enrollToken = '';
String? _postToken = '';
late String _token;
dynamic getPolicyNo;
dynamic branchName;
// bool _isLoading = false;
dynamic getPolicyNameDetails;
dynamic enrollmentClient_id;
dynamic policy_name;
dynamic getCardArrays = [];
List<dynamic> empAllowed_modules = [];
List<Widget> visibleTabs = [];
List<Widget> tabViews = [];
dynamic clientName;
dynamic clientLogo;
dynamic empClientBranchId;
dynamic empHrId;
List<dynamic> enrollmentAllowed_modules = [];
List<Map<String, dynamic>> tabData = [];
dynamic enrollmentEmpClientBranchId;
dynamic enrollmentHrId;
dynamic empClientId;
String empCodeFromHrPolcy = '';
final List<Color> cardColors = [
Color(0xFFFFE3D9),
Color(0xFFFFD9EE),
Color(0xFFDBFFDE),
Color(0xFFE4DFFF),
];
ScrollController _scrollController = ScrollController();
int selectedIndex = 0;
final tokenService = TokenStorageService();
@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();
_tabController.dispose();
}
checkToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// enrollToken = prefs.getString('pre_enrollment_data');
// _postToken = prefs.getString('post_enrollment_data');
// Get the current token
final token = await tokenService.getCurrentToken();
print('token - $token');
print('token check in');
if((token != null && token!.isNotEmpty)){
print('token check done');
_loadToken();
} else {
print('token check reject');
ToastHelper.showErrorToast(context, 'Session Out');
Navigator.pushReplacementNamed(context, 'hrLogin');
}
}
Future<void> _loadToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
enrollToken = await tokenService.getCurrentToken();
_postToken = await tokenService.getCurrentToken();
print(enrollToken);
print(_postToken);
branchName = prefs.getString('branchName') ?? '';
if (enrollToken != null && enrollToken!.isNotEmpty) {
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(enrollToken!);
// enrollmentClient_id = decodedToken['client_id'].toString();
enrollmentClient_id = prefs.getString('enrollmentClient_id');
enrollmentEmpClientBranchId =
prefs.getString('enrollmentEmpClientBranchId');
enrollmentHrId = prefs.getString('enrollmentHrId');
// print("Pre decodedToken - $decodedToken");
print("Pre enrollmentClient_id - $enrollmentClient_id");
print("Pre enrollmentEmpClientBranchId - $enrollmentEmpClientBranchId");
print("Pre enrollmentHrId - $enrollmentHrId");
String? modulesString = prefs.getString('enrollmentAllowed_modules');
if (modulesString != null) {
enrollmentAllowed_modules = jsonDecode(modulesString);
print(
"enrollmentAllowed_modules - $enrollmentAllowed_modules"); // [2, 3, 4]
if (enrollmentAllowed_modules.contains(1)) {
tabData.add({
'icon': Icons.grid_view,
'label': 'Pre Enrollment',
});
print("getCardArrays.length = ${getCardArrays.length}");
// tabViews.add(
// PreEnrollment(
// enrollmentClientId: enrollmentClient_id,
// enrollmentClientBranchId: enrollmentEmpClientBranchId,
// enrollmentHrId: enrollmentHrId,
// enrollToken: enrollToken,
// ),
// );
}
}
if (prefs.containsKey('clientLogo') && prefs.containsKey('clientName')) {
clientLogo = prefs.getString('clientLogo');
clientName = prefs.getString('clientName');
} else {
empClientId = prefs.getString('empClientId');
empClientBranchId = prefs.getString('empClientBranchId');
getClientLogoAndDetails(empClientId,empClientBranchId,
enrollmentEmpClientBranchId, enrollmentClient_id, enrollToken);
}
getCashDepositDetails(enrollmentEmpClientBranchId, enrollmentClient_id,
enrollmentHrId, enrollToken);
}
if (_postToken != null && _postToken!.isNotEmpty) {
// Map<String, dynamic>? postdecodedToken = Jwt.parseJwt(_postToken!);
// empClientId = postdecodedToken['client_id'].toString();
empClientId = prefs.getString('empClientId');
empClientBranchId = prefs.getString('empClientBranchId');
empHrId = prefs.getString('empHrId');
print('post empHrId- $empHrId');
// print('postdecodedToken- $postdecodedToken');
print('post empClientId- $empClientId');
print('post empClientBranchId- $empClientBranchId');
print('allowed_modules');
String? modulesString = prefs.getString('empAllowed_modules');
// String? enrollmodulesString =
// prefs.getString('enrollmentAllowed_modules');
if (modulesString != null) {
empAllowed_modules = jsonDecode(modulesString);
// enrollmentAllowed_modules = jsonDecode(enrollmodulesString!);
print("empAllowed_modules - $empAllowed_modules"); // [2, 3, 4]
// print(
// "enrollmentAllowed_modules - $enrollmentAllowed_modules"); // [2, 3, 4]
if (empAllowed_modules.contains(2)) {
print("visibleTabs.length2");
print(visibleTabs.length);
tabData.add({
'icon': Icons.verified_user,
'label': 'Active Policies',
});
// tabViews.add(
// ActivePolicies(
// empClientId: empClientId,
// empClientBranchId: empClientBranchId,
// empHrId: empHrId,
// postToken: _postToken,
// ),
// );
}
if (empAllowed_modules.contains(3)) {
print("visibleTabs.length3");
print(visibleTabs.length);
tabData.add({
'icon': Icons.desktop_windows,
'label': 'CD',
});
// tabViews.add(CdPolicies(
// empClientId: empClientId,
// empClientBranchId: empClientBranchId,
// empHrId: empHrId,
// postToken: _postToken,
// ));
}
print('333');
if (empAllowed_modules.contains(4)) {
tabData.add({
'icon': Icons.receipt_long,
'label': 'Claims', // Label Name Integrated with code (dont change)
});
// tabViews.add(ClaimsPolicies(
// empClientId: empClientId,
// empClientBranchId: empClientBranchId,
// empHrId: empHrId,
// postToken: _postToken,
// isHrcode: widget.isHrcode == 1 ? 1 : 0,
// empCodeHrPolicy: empCodeFromHrPolcy
// // empCodeHrPolicy: widget.empCodeFromHrPolicy
// ));
}
}
// getCashDepositDetails(
// empClientBranchId, empClientId, empHrId, _postToken);
if (prefs.containsKey('clientLogo') && prefs.containsKey('clientName')) {
clientLogo = prefs.getString('clientLogo');
clientName = prefs.getString('clientName');
} else {
empClientId = prefs.getString('empClientId');
empClientBranchId = prefs.getString('empClientBranchId');
getClientLogoAndDetails(empClientId,empClientBranchId,
enrollmentEmpClientBranchId, enrollmentClient_id, _postToken);
}
print("getCashDepositDetails");
}
_tabController = TabController(length: tabData.length, vsync: this);
print('selectedIndex ${widget.selectedIndex}');
// var isHrcode;
print('111');
if (widget.selectedIndex == 3) {
print("Process1");
print("ProcessTAb - $tabData");
final claimsIndex = tabData.indexWhere((tab) => tab['label'] == 'Claims');
print('Claims tab index: $claimsIndex');
_tabController.index = claimsIndex;
// _tabController.index = 3;
setState(() {
selectedIndex = claimsIndex;
// selectedIndex = 3;
isHrcode = 1;
empCodeFromHrPolcy = widget.empCodeFromHrPolicy;
});
print("tabIndexConti11- $selectedIndex - ${_tabController.index}");
// print("tabIndexCode11- $isHrcode - ${widget.isHrempcode}");
}
print('222');
_tabController.addListener(() {
int newIndex = _tabController.index;
setState(() {
selectedIndex = newIndex;
if (newIndex != 3 && empCodeFromHrPolcy.isNotEmpty) {
empCodeFromHrPolcy = ''; // clear only once
}
// selectedIndex = _tabController.index;
// empCodeFromHrPolcy = '';
print("Process2");
print("tabIndexConti- $selectedIndex - ${_tabController.index}");
// print("tabIndexCode11- $isHrcode ");
});
});
setState(() {});
isLoading = false;
}
List<Widget> getTabViews() {
return tabData.map((tab) {
final label = tab['label'];
switch (label) {
case 'Claims':
return ClaimsPolicies(
empClientId: empClientId,
empClientBranchId: empClientBranchId,
empHrId: empHrId,
postToken: _postToken!,
isHrcode: widget.isHrcode == 1 ? 1 : 0,
empCodeHrPolicy: empCodeFromHrPolcy,
);
case 'CD':
return CdPolicies(
empClientId: empClientId,
empClientBranchId: empClientBranchId,
empHrId: empHrId,
postToken: _postToken!,
);
case 'Active Policies':
return ActivePolicies(
empClientId: empClientId,
empClientBranchId: empClientBranchId,
empHrId: empHrId,
postToken: _postToken!,
);
case 'Pre Enrollment':
return PreEnrollment(
enrollmentClientId: enrollmentClient_id,
enrollmentClientBranchId: enrollmentEmpClientBranchId,
enrollmentHrId: enrollmentHrId,
enrollToken: enrollToken!,
);
default:
return Center(child: Text('Unknown tab'));
}
}).toList();
}
Future<void> getClientLogoAndDetails(post_client_id, post_client_branch_id,
pre_client_branch_id, pre_client_id, token) async {
var url = Uri.parse(Environment.apiUrl +
'getClientDetails?post_client_id=$post_client_id&post_branch_id=$post_client_branch_id&pre_client_id=$pre_client_id&pre_branch_id=$pre_client_branch_id');
try {
var response = await http.get(
url,
headers: {
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
'Authorization':
'Bearer $token', // Add token to the Authorization header
},
);
if (response.statusCode == 200) {
print('response.statusCode == 200');
Map<String, dynamic> data = json.decode(response.body);
print(data);
if (data.containsKey('data')) {
dynamic clientDetails = data['data'];
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('clientLogo', clientDetails['client']['client_logo']);
prefs.setString('clientName', clientDetails['client']['client_name']);
setState(() {
// dynamic clientDetails = data['data'];
// print(clientDetails);
clientName = clientDetails['client']['client_name'];
print(clientName);
clientLogo = clientDetails['client']['client_logo'];
print(clientLogo);
});
} else {
// Handle other status messages if needed
// ToastHelper.showErrorToast(
// context, 'API request failed with status: ${data['status']}');
print('API request failed with status: ${data['status']}');
}
} else {
// Handle other status codes
// ToastHelper.showErrorToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response.statusCode}');
}
} catch (e) {
// Handle exceptions
print('Exception occurred: $e');
}
}
Future<void> getCashDepositDetails(
clintBranchId, clintID, hr_id, token) async {
print('IN');
print("clintBranchId -$clintBranchId");
print("clintID -$clintID");
print("hr_id -$hr_id");
print("token -$token");
isLoading = true;
// setState(() {
// _isLoading = true;
// });
try {
if (clintBranchId == null || clintID == null) {
return;
}
final response = await apiService.getCashDepositDetailsToApi(
clintID!, clintBranchId!, hr_id, token);
// final response = await apiService.getCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token);
print('IN1');
if (response['status'] == 'success') {
isLoading = false;
setState(() {
print('response');
print(response['data']);
getCardArrays = List<Map<String, dynamic>>.from(response['data']);
print('getCardArrays');
print(getCardArrays);
});
print('IN2');
} else {
print('API request failed with status');
}
} catch (e) {
print('Exception occurred: $e');
}
}
Future<void> downloadExcel(clintId, insurerId, insurerName) async {
// API endpoint to download the Excel file
// Send GET request to the API
var url = Uri.parse(Environment.apiUrl +
'exportCashDepositData?client_id=$clintId&insurer_id=$insurerId');
var response = await http.get(
url,
headers: {
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
'Authorization': 'Bearer $_token',
},
);
// Check if the request was successful (status code 200)
// Check if the request was successful (status code 200)
// Check if the request was successful (status code 200)
if (response.statusCode == 200) {
// Create a blob from the response body
final blob = html.Blob([response.bodyBytes]);
// Generate a download URL for the blob
final url = html.Url.createObjectUrlFromBlob(blob);
// Create a link element to trigger the download
final anchor = html.AnchorElement(href: url)
..setAttribute('download', '$insurerName.xlsx')
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
} else {
// Handle error
print('Failed to download Excel file: ${response.statusCode}');
}
}
final List<Map<String, dynamic>> policies = List.generate(10, (index) {
return {
"policyNo": index % 2 == 0
? "GMC - S70000/48/2025/401"
: "GMC - 4016/X0/351234479/00/000",
"insurer": "ICICI Lombard General Insurance Company Limited",
"draft": "1234",
"enrolled": "4",
"total": "1234",
};
});
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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
if (getCardArrays == []) {
return PopScope(
canPop: false, // Prevents automatic back navigation
onPopInvoked: (didPop) async {
// This is triggered when browser back button is pressed
bool logout = await _showLogoutDialog();
if (logout) {
// Clear storage & logout
// Example:
// await storage.clear();
final prefs = await SharedPreferences.getInstance();
prefs.clear();
print('LocalStorage Cleared');
Navigator.pushNamed(context, 'hrLogin');
}
},
child: Scaffold(
appBar: CustomAppBar(),
body: SingleChildScrollView(
child: Container(
color: Color(0xFFEFF3F6),
child: Column(
children: [
Center(
child: Text('No Data Available'),
)
],
),
),
)));
} else {
int numCards = getCardArrays.length;
int numExpanded = numCards < 4 ? numCards : 4;
double cardHeight = MediaQuery.of(context).size.height / 4.5;
double policyHeight = MediaQuery.of(context).size.height / 4.5;
double cardWidth = MediaQuery.of(context).size.width / 6;
print("visibleTabAll - $visibleTabs");
print("tabViewsAll - $tabViews");
return PopScope(
canPop: false, // Prevents automatic back navigation
onPopInvoked: (didPop) async {
// This is triggered when browser back button is pressed
bool logout = await _showLogoutDialog();
if (logout) {
// Clear storage & logout
// Example:
// await storage.clear();
final prefs = await SharedPreferences.getInstance();
prefs.clear();
print('LocalStorage Cleared');
Navigator.pushNamed(context, 'hrLogin');
}
},
child: Scaffold(
appBar: CustomAppBar(),
body: Stack(children: [
Container(
padding: const EdgeInsets.only(
top: 20, bottom: 40, left: 40, right: 40),
// padding: const EdgeInsets.only(
// top: 20, bottom: 20, left: 50, right: 50),
color: const Color(0xFFEFF3F6),
child: Column(
children: [
Container(
color: Colors.white,
width: double.infinity,
// height: 75,
height: MediaQuery.of(context).size.height * 0.12,
padding: Responsive.isDesktop(context)
? const EdgeInsets.only(
top: 10, bottom: 10, left: 20, right: 20)
: EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10), // Add padding to the container
child: Row(
children: [
Expanded(
flex: 6,
child: Container(
width: 150,
height: 80,
alignment: Alignment.centerLeft,
child: Image.network(
Uri.encodeFull(clientLogo ?? ''),
width: 200,
height: 200,
fit: BoxFit.contain,
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) {
return child;
}
return AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(milliseconds: 500),
curve: Curves.easeOut,
child: child,
);
},
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
(loadingProgress.expectedTotalBytes ?? 1)
: null,
),
),
);
},
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
return Text('');
// Image.asset(
// 'assets/Solid_gray.png',
// width: 80,
// height: 60,
// fit: BoxFit.cover,
// );
},
),
),
),
Expanded(
flex: 9,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end, // right aligned
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Client Name
Text(
clientName ?? '',
textAlign: TextAlign.right,
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 20 : 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 20),
// Branch Name (new line)
Text(
'Branch: ${branchName}' ?? '',
textAlign: TextAlign.right,
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 16 : 14,
fontWeight: FontWeight.w400,
color: Colors.grey[700],
),
),
],
),
)
],
),
),
SizedBox(height: 8),
tabData.isNotEmpty
? Container(
// padding: const EdgeInsets.all(5),
// height: MediaQuery.of(context).size.height * 0.08,
decoration: BoxDecoration(
color: Color(
0xFFC4E3E6), // 🔹 Background behind the TabBar
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.transparent),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 4,
offset: const Offset(0, 4),
),
],
),
child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// mainAxisSize: MainAxisSize.min,
children: [
TabBar(
controller: _tabController,
isScrollable: true,
labelPadding: EdgeInsets.zero,
dividerColor: Colors.transparent,
indicatorColor: Colors.transparent,
indicatorPadding: EdgeInsets.zero,
labelColor: Colors.white,
unselectedLabelColor: const Color(0xFF828282),
// tabs: visibleTabs,
tabs: List.generate(tabData.length, (index) {
final data = tabData[index];
return CustomTab(
icon: data['icon'],
label: data['label'],
isSelected: selectedIndex == index,
);
}),
),
],
),
)
: Container(
height: MediaQuery.of(context).size.height * 0.6,
// color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/claimsData.png', // Replace 'default_image.png' with your default image asset path
width: 300,
height: 300,
fit: BoxFit.cover,
),
const Text("No Data Available"),
],
),
),
SizedBox(height: 16),
// Expanded TabBarView
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: TabBarView(
controller: _tabController,
children: getTabViews(),
// children: tabViews,
),
),
),
],
),
),
if (isLoading)
Container(
color: Color(0x98FFFCE5), // 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
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity, // Make the footer full width
child: CustomFooter(),
),
),
]))
);
}
}
Widget buildPolicyCard(Map<String, dynamic> policy) {
print("buildPolicyCard - $policy");
return SizedBox(
height: 50,
child: Card(
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
policy['type'] ?? '',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(height: 4),
Text(
policy['policy_name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildCountBox(
policy['membersCountOfDraft'].toString(), "Draft"),
_buildCountBox(
policy['membersCountOfEnrolled'].toString(), "Enrolled"),
_buildCountBox(
policy['totalMembersCount'].toString(), "Total"),
],
),
],
),
),
),
);
}
Widget _buildCountBox(String count, String label) {
return Column(
children: [
Container(
width: 50,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFFDFF1F3),
borderRadius: BorderRadius.circular(8),
),
child: Text(
count,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
const SizedBox(height: 6),
Text(label,
style: const TextStyle(fontSize: 12, color: Colors.black87)),
],
);
}
}
class CustomTab extends StatelessWidget {
final IconData icon;
final String label;
final bool isSelected;
const CustomTab({
required this.icon,
required this.label,
required this.isSelected,
});
@override
Widget build(BuildContext context) {
print("isSelected - $isSelected");
return Tab(
child: Container(
// margin: const EdgeInsets.symmetric(horizontal: 6),
margin: const EdgeInsets.only(right: 6),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF00999E) : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
if (isSelected)
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
)
else
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon,
size: 18,
color: isSelected ? Colors.white : const Color(0xFF828282)),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
color: isSelected ? Colors.white : const Color(0xFF828282),
),
),
],
),
),
);
}
}
class PolicyCard extends StatelessWidget {
final Map<String, dynamic> policy;
const PolicyCard({required this.policy});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(policy["policyNo"],
style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(policy["insurer"],
style: const TextStyle(fontSize: 11, color: Colors.grey)),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildStatusBox("Draft", policy["draft"]),
_buildStatusBox("Enrolled", policy["enrolled"]),
_buildStatusBox("Total", policy["total"]),
],
)
],
),
),
);
}
Widget _buildStatusBox(String label, String value) {
return Column(
children: [
Text(value,
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.teal)),
const SizedBox(height: 2),
Text(label, style: const TextStyle(fontSize: 11, color: Colors.grey)),
],
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:nhancepolicy/customAppBar/customAppBar.dart';
import 'package:nhancepolicy/excel_verification.dart';
import 'package:nhancepolicy/presentation/preFileUpload.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'dart:convert';
@ -353,7 +353,7 @@ class _MyHrHomeState extends State<MyHrHome> with TickerProviderStateMixin {
// argumentDetails = 'Dependent-AddOn';
// }
Navigator.pushNamed(context, 'excelVerify', arguments: argumentDetails);
Navigator.pushNamed(context, 'preFileUpload', arguments: argumentDetails);
return;
if (kIsWeb) {
final input = html.FileUploadInputElement();

View File

@ -9,9 +9,9 @@ import 'package:nhancepolicy/hrVerify.dart';
import 'dart:convert';
import 'dart:io';
import 'package:nhancepolicy/responsive.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_animated_button/flutter_animated_button.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'config/environment.dart';
import 'email_verify.dart';
@ -39,6 +39,7 @@ class _MyPhoneState extends State<MyHrLogin> {
int? _resendToken;
bool _isLoading = false;
bool isEmailFieldVisible = false;
final tokenService = TokenStorageService();
@override
void initState() {
@ -48,17 +49,18 @@ class _MyPhoneState extends State<MyHrLogin> {
clearLocalStorageWhenStarts('initState');
}
clearLocalStorageWhenStarts(fromData) async {
print(fromData);
// if(kIsWeb) {
print('vndbbcbdskbvkj3');
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
print('Local Storage Clear');
// }
print('Clearing secure storage on app start');
await tokenService.clearAll();
print('Secure Storage Cleared');
}
void toggleField() {
setState(() {
isEmailFieldVisible = !isEmailFieldVisible;
@ -66,8 +68,7 @@ class _MyPhoneState extends State<MyHrLogin> {
}
Future<void> verifyMobileAndEmailNumber() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
await tokenService.clearAll();
print('verifyMobileAndEmailNumber :- Local Storage Clear');
try {
if (_formKey.currentState!.validate()) {
@ -115,14 +116,16 @@ class _MyPhoneState extends State<MyHrLogin> {
bool userVerification = data['data']['user_verification'];
String message = data['data']['message'];
if (userVerification) {
final SharedPreferences prefs =
await SharedPreferences.getInstance();
if (isEmailFieldVisible) {
print('isEmailFieldVisible $isEmailFieldVisible');
prefs.setString('empEmail', emailMobileController.text);
print('${emailMobileController.text}');
ToastHelper.showSuccessToast(
context, 'Verification code sent to ${emailMobileController.text}');
await tokenService.writeValue(
'empEmail',
emailMobileController.text,
);
// print('${emailMobileController.text}');
// final message = 'Verification code sent to ${emailMobileController.text}';
ToastHelper.showSuccessToast(context, 'Verification code sent to ${emailMobileController.text}');
Navigator.push(
context,
MaterialPageRoute(
@ -133,8 +136,8 @@ class _MyPhoneState extends State<MyHrLogin> {
),
);
} else {
ToastHelper.showSuccessToast(
context, 'Verification code sent to ${emailMobileController.text}');
// final message = 'Verification code sent to ${emailMobileController.text}';
ToastHelper.showSuccessToast(context, 'Verification code sent to ${emailMobileController.text}');
Navigator.push(
context,
MaterialPageRoute(
@ -154,6 +157,13 @@ class _MyPhoneState extends State<MyHrLogin> {
ToastHelper.showErrorToast(context, message);
print('Invalid mobile number');
}
} else if (response.statusCode == 429) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
final message = data['message'];
ToastHelper.showErrorToast(context, message);
} else {
setState(() {
_isLoading = false;
@ -297,606 +307,267 @@ class _MyPhoneState extends State<MyHrLogin> {
@override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
EdgeInsets marginInsets = EdgeInsets.zero;
final Size _size = MediaQuery.of(context).size;
if (Responsive.isDesktop(context)) {
marginInsets = const EdgeInsets.only(
left: 0,
right: 0,
bottom: 0,
top: 0,
);
} else if (Responsive.isMobile(context)) {
marginInsets = const EdgeInsets.only(
left: 25, // Example value for mobile
right: 25, // Example value for mobile
bottom: 0, // Example value for mobile
top: 0, // Example value for mobile
);
} else if (Responsive.isTablet(context)) {
marginInsets = const EdgeInsets.only(
left: 25, // Example value for mobile
right: 25, // Example value for mobile
bottom: 0, // Example value for mobile
top: 0, // Example value for mobile
);
}
return Scaffold(
body: SingleChildScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Container(
height: _size.height,
color: Colors.white,
child: Stack(
children: [
Visibility(
visible: _size.width <= 1100,
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
child: Container(
height: _size.height / 3,
width: double.infinity,
color: Color(0xFF00989E),
child: Stack(
children: [
Positioned(
top: 40, // Adjust top position as needed
left: 10, // Align to the right
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
// Add your navigation logic here
// For example, you can use Navigator.push to navigate to another page
Navigator.pushNamed(context, 'hrLogin');
},
body: Container(
width: double.infinity,
height: _size.height,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Color(0xFF00B6AC),
Color(0xFF83E0DE),
Color(0xFF01B4A8),
],
),
),
child: Center(
child: Container(
width: double.infinity, // fixed web width
height: _size.height, // fixed web height (IMPORTANT)
margin: const EdgeInsets.all(60),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(40)),
),
child: Row(
children: [
// ================= LEFT IMAGE =================
Expanded(
flex: 5,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(40),
bottomLeft: Radius.circular(40),
),
child: Image.asset(
'assets/hrLogin.png',
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
),
),
// ================= RIGHT FORM =================
Expanded(
flex: 7,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 300,
height: 100,
),
SizedBox(height: 25),
Container(
margin: EdgeInsets.symmetric(
horizontal: 150),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
Icons.west, // Icon for customer login
color: Colors
.black, // Adjust color as needed
),
SizedBox(width: 5),
Text(
'Member Login',
style: TextStyle(
color: Color(0xFF000000), // Text color
// Add other text styles as needed
Expanded(
child: Text(
"Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family",
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000)),
textAlign: TextAlign.center,
),
),
)
],
),
),
),
),
),
Column(
children: [
SizedBox(
height: _size.height /
6.4), // Adjust the spacing between the rows
Row(
mainAxisAlignment: MainAxisAlignment
.center, // Align to the center
children: [
Expanded(
flex: Responsive.isDesktop(context) ? 10 : 12,
child: Align(
alignment: Responsive.isDesktop(context)
? Alignment.centerLeft
: Alignment.bottomCenter,
child: Image.asset(
_size.width <= 1100
? 'assets/mobileViewLogo.png'
: 'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 150,
),
),
SizedBox(height: 20),
Container(
height: 55,
margin: const EdgeInsets.symmetric(horizontal: 150) ,
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
if (!Responsive.isMobile(context) &&
!Responsive.isTablet(context))
Expanded(
flex: 2,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
// Add your navigation logic here
// For example, you can use Navigator.push to navigate to another page
Navigator.pushNamed(
context, 'hrLogin');
},
child: Row(
mainAxisAlignment: MainAxisAlignment
.end, // Align to the end (right)
children: [
Text(
'HR Login',
style: TextStyle(
color: Color(
0xFF000000), // Text color
// Add other text styles as needed
),
),
SizedBox(width: 5),
Icon(
Icons
.east, // Icon for customer login
color: Colors
.black, // Adjust color as needed
),
],
),
),
child: TextFormField(
controller: emailMobileController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Email / Mobile Number ",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
],
),
],
),
],
),
),
),
),
Container(
margin: marginInsets,
alignment: Alignment.bottomCenter,
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
Row(
children: [
if (_size.width > 1100)
Expanded(
flex: _size.width < 1100 ? 6 : 12,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints constraints) {
if (constraints.maxWidth > 600) {
return Image.asset(
'assets/hrLogin.jpg',
height: _size.height,
fit: BoxFit.cover,
);
} else {
return SizedBox();
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Please enter email or mobile number";
}
String input = value.trim();
// Reject all spaces
if (input.contains(' ')) {
return "No spaces allowed";
}
final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$');
final mobileRegex = RegExp(r'^[0-9]{10}$');
bool isEmailFormat = emailRegex.hasMatch(input);
bool isMobileFormat = mobileRegex.hasMatch(input);
// ---------------------------
// 🛑 MOBILE VALIDATION
// ---------------------------
if (RegExp(r'^[0-9]+$').hasMatch(input)) {
if (input.length != 10) {
return "Mobile number must be exactly 10 digits";
}
}
// ---------------------------
// 🛑 EMAIL VALIDATION
// ---------------------------
// Reject anything that has '@' but is NOT a valid email format
if (input.contains('@') && !isEmailFormat) {
return "Enter a valid email address";
}
// Reject email with extra digits at the end
if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) {
return "Email cannot contain extra numbers";
}
// Reject email+mobile combination
if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) {
return "Enter only email OR mobile number";
}
// ---------------------------
// 🛑 MIXED CONTENT (letters + digits but NOT email)
// ---------------------------
bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input);
bool hasDigits = RegExp(r'[0-9]').hasMatch(input);
if ((hasLetters && hasDigits) && !input.contains('@')) {
return "Enter only email OR 10-digit mobile number";
}
// ---------------------------
// 🟢 FINAL CHECK
// ---------------------------
if (!isEmailFormat && !isMobileFormat) {
return "Enter a valid email or 10-digit mobile number";
}
return null;
}
},
),
),
Expanded(
flex: _size.width < 1100 ? 6 : 12,
child: Container(
margin: _size.width > 1100
? EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(left: 0, right: 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!Responsive.isMobile(context) &&
!Responsive.isTablet(context))
Row(
children: [
// Expanded(
// flex: 4,
// child: Align(
// alignment: Alignment.centerLeft,
// child: AnimatedButton(
// animatedOn:
// AnimatedOn.onHover,
// onPress: () {
// Navigator.pushNamed(
// context, 'phone');
// },
// onChanges: (change) {},
// height: 30,
// width: 150,
// text: 'Member Login',
// isReverse: true,
// selectedTextColor:
// Colors.black,
// transitionType: TransitionType
// .RIGHT_CENTER_ROUNDER,
// textStyle:
// GoogleFonts.poppins(
// fontSize: 16,
// letterSpacing: 0,
// color: Color(0xFF00989E),
// fontWeight: FontWeight.w300,
// ),
// backgroundColor: Colors.white,
// selectedBackgroundColor:
// Color(0xFF00989E),
// borderColor:
// Color(0xFF00989E),
// borderWidth: 1,
// ),
// )),
Expanded(
flex: 8,
child: Align(
alignment: Alignment.center, // Align to the start
child: _size.width <= 1100
? Image.asset(
'assets/Nhance-Logo-Final-mobile.png',
width: 150,
height: 70,
)
: _size.width > 1100
? Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 70,
)
: Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 150,
height: 70,
),
)),
],
),
SizedBox(height: 80),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Expanded(
child: Text(
"Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family",
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000)),
textAlign: TextAlign.center,
),
)
],
SizedBox(height: 20),
Container(
margin: EdgeInsets.symmetric(
horizontal: 150),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius
.circular(10),
),
),
SizedBox(
height: 20,
onPressed: _isLoading
? null
: verifyMobileAndEmailNumber,
child: _isLoading
? const CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<
Color>(
Color(0xFF00989E),
),
)
: Text( "Login with Email / Mobile OTP",
style: GoogleFonts
.poppins(
color: const Color(
0xFFFFFFFF),
),
),
Column(
children: [
Container(
height: 55,
margin: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 150)
: const EdgeInsets.symmetric(horizontal: 0),
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
controller: emailMobileController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Email / Mobile Number ",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Please enter email or mobile number";
}
String input = value.trim();
// Reject all spaces
if (input.contains(' ')) {
return "No spaces allowed";
}
final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$');
final mobileRegex = RegExp(r'^[0-9]{10}$');
bool isEmailFormat = emailRegex.hasMatch(input);
bool isMobileFormat = mobileRegex.hasMatch(input);
// ---------------------------
// 🛑 MOBILE VALIDATION
// ---------------------------
if (RegExp(r'^[0-9]+$').hasMatch(input)) {
if (input.length != 10) {
return "Mobile number must be exactly 10 digits";
}
}
// ---------------------------
// 🛑 EMAIL VALIDATION
// ---------------------------
// Reject anything that has '@' but is NOT a valid email format
if (input.contains('@') && !isEmailFormat) {
return "Enter a valid email address";
}
// Reject email with extra digits at the end
if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) {
return "Email cannot contain extra numbers";
}
// Reject email+mobile combination
if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) {
return "Enter only email OR mobile number";
}
// ---------------------------
// 🛑 MIXED CONTENT (letters + digits but NOT email)
// ---------------------------
bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input);
bool hasDigits = RegExp(r'[0-9]').hasMatch(input);
if ((hasLetters && hasDigits) && !input.contains('@')) {
return "Enter only email OR 10-digit mobile number";
}
// ---------------------------
// 🟢 FINAL CHECK
// ---------------------------
if (!isEmailFormat && !isMobileFormat) {
return "Enter a valid email or 10-digit mobile number";
}
return null;
}
),
),
),
SizedBox(height: 20),
Container(
width: double
.infinity, // Make the footer full width
child: Container(
alignment: Alignment.bottomCenter,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: <TextSpan>[
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: Color(0xFFE26828),
fontSize: 9,
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(
context)
? EdgeInsets.symmetric(
horizontal: 150)
: EdgeInsets.symmetric(
horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor:
Color(0xFF00989E),
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius
.circular(10),
),
),
onPressed: _isLoading
? null
: verifyMobileAndEmailNumber,
child: _isLoading
? CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<
Color>(
Color(0xFF00989E),
),
)
: Text( "Login with Email / Mobile OTP",
style: GoogleFonts
.poppins(
color: Color(
0xFFFFFFFF),
),
),
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: Color(0xFFE26828),
fontSize: 9,
),
),
SizedBox(height: 15),
// MouseRegion(
// cursor: SystemMouseCursors.click,
// child: GestureDetector(
// onTap: toggleField,
// child: Text(
// isEmailFieldVisible
// ? "Login with Mobile No"
// : "Login with Email",
// style: TextStyle(
// color: Color(0xFF00989E),
// ),
// ),
// ),
// ),
],
),
SizedBox(
height: _size.width <= 1100 ? 0 : 0,
),
SizedBox(
height: _size.width <= 1100 ? 0 : 0,
),
SizedBox(
height: _size.width <= 1100 ? 0 : 0,
),
// _size.width > 1100
// ? Container(
// margin: EdgeInsets.symmetric(
// horizontal: 150),
// child: Column(
// children: [
// SizedBox(height: 30),
// Text(
// "Benefits of Login",
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// ),
// ),
// SizedBox(height: 15),
// ],
// ))
// : SizedBox(),
// _size.width > 1100
// ? Container(
// margin: EdgeInsets.symmetric(
// horizontal: 150),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// Expanded(
// flex: 6,
// child: Container(
// padding:
// EdgeInsets.symmetric(
// vertical: 8),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment
// .center,
// children: [
// Expanded(
// child: Container(
// padding: EdgeInsets
// .symmetric(
// vertical:
// 12),
// decoration:
// BoxDecoration(
// border: Border(
// right:
// BorderSide(
// width: 1,
// color: Colors
// .black,
// ),
// ),
// ),
// child: Column(
// children: [
// Icon(
// Icons
// .policy,
// color: Color(
// 0xFFE26728)),
// SizedBox(
// height: 10),
// Text(
// "View Policy"),
// ],
// ),
// ),
// ),
// Expanded(
// child: Container(
// padding: EdgeInsets
// .symmetric(
// vertical:
// 12),
// child: Column(
// children: [
// Icon(Icons.edit,
// color: Color(
// 0xFFE26728)),
// SizedBox(
// height: 10),
// Text(
// "Manage Claims"),
// ],
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// )
// : SizedBox(
// height:
// Responsive.isDesktop(context)
// ? _size.height * 0.1
// : _size.height * 0.2,
// ),
SizedBox(
height: Responsive.isDesktop(context)
? _size.height * 0.3
: _size.height * 0.2,
),
// SizedBox(
// height: _size.height * 0.1,
// ),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double
.infinity, // Make the footer full width
child: Container(
alignment: Alignment.bottomCenter,
padding:
EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: <TextSpan>[
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
],
),
),
),
),
),
],
),
),
),
),
],
],
),
),
],
),
),
),
],
),
),
],
)),
));
)
)
);
}
}

View File

@ -5,23 +5,27 @@ import 'package:nhancepolicy/addons.dart';
import 'package:nhancepolicy/branch/branch_selection_page.dart';
import 'package:nhancepolicy/empReview.dart';
import 'package:nhancepolicy/empDetails.dart';
import 'package:nhancepolicy/excel_verification.dart';
import 'package:nhancepolicy/presentation/preFileUpload.dart';
import 'package:nhancepolicy/hrHome.dart';
import 'package:nhancepolicy/hrVerify.dart';
import 'package:nhancepolicy/phone.dart';
import 'package:nhancepolicy/postFileUpload.dart';
import 'package:nhancepolicy/presentation/excelVerification.dart';
import 'package:nhancepolicy/presentation/postFileUpload.dart';
import 'package:nhancepolicy/presentation/cdList.dart';
import 'package:nhancepolicy/presentation/claims.dart';
import 'package:nhancepolicy/presentation/policies.dart';
import 'package:nhancepolicy/service/session/session_service.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:nhancepolicy/verify.dart';
import 'package:nhancepolicy/home.dart';
import 'package:nhancepolicy/hrDashboard.dart';
import 'package:nhancepolicy/presentation/hrDashboard.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:nhancepolicy/hrLogin.dart';
import 'package:nhancepolicy/hrPolicyDetails.dart';
import 'package:nhancepolicy/presentation/hrPolicyDetails.dart';
import 'package:nhancepolicy/oldPolicy.dart';
import 'package:firebase_core/firebase_core.dart';
import 'cdTransactionDetails.dart';
import 'presentation/cdTransactionDetails.dart';
import 'config/environment.dart';
import 'email_verify.dart';
@ -96,7 +100,7 @@ Future<void> startApp() async {
// onResendCode: (String, int) {},
// ),
'hrHome': (context) => MyHrHome(),
'excelVerify': (context) => const excelVerify(
'preFileUpload': (context) => const preFileUpload(
ClientId: '',
policyTypeId: '',
ClientPoliyId: '',
@ -108,6 +112,7 @@ Future<void> startApp() async {
cardInsurer_name: '',
cardPolicy_name: '',
cardPolicy_ExpDate: '',
total_premium: '',
),
'postFileUpload': (context) => const postFileUpload(
ClientId: '',
@ -121,23 +126,22 @@ Future<void> startApp() async {
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) => hrDashboard(
selectedIndex: 0,
isHrcode: 0,
empCodeFromHrPolicy: '',
),
'cdTransactionDetails': (context) => cdTransactionDetails(
insurerName: '',
cdMasterAccountNo: '',
insurerId: '',
cd_ac_pk: '',
empClientId: '',
postToken: '',
),
'hrDashboard': (context) => hrDashboard(),
'hrPolicyDetails': (context) => hrPolicyDetails(
ClientId: '',
policyTypeId: '',
@ -150,9 +154,21 @@ Future<void> startApp() async {
cardInsurer_name: '',
cardPolicy_name: '',
cardPolicy_ExpDate: '',
total_premium: '',
is_ecard_bulk_download_for_employee: 0,
),
'oldPolicy': (context) => oldPolicy(),
'branchSelection': (context) => BranchSelectionPage(),
'policies': (context) => policies(),
'CdPoliciesList': (context) => CdPoliciesList(),
'ClaimsPolicies': (context) => ClaimsPolicies(),
'cdTransactionDetails': (context) => cdTransactionDetails(
insurerName: '',
cdMasterAccountNo: '',
insurerId: '',
cd_ac_pk: '',
empClientId: '',
),
},
));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,496 @@
import 'dart:convert';
import 'package:csv/csv.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import 'cdTransactionDetails.dart';
import 'package:collection/collection.dart';
import '../customAppBar/base_layout.dart';
import '../service/api_service.dart';
import '../service/token_storage_service.dart';
class CdPoliciesList extends StatefulWidget {
const CdPoliciesList({Key? key}) : super(key: key);
@override
State<CdPoliciesList> createState() => _CdPoliciesListState();
}
class _CdPoliciesListState extends State<CdPoliciesList> {
final tokenService = TokenStorageService();
Uint8List? fileBytes;
List<Map<String, dynamic>> getCDPolicies = [];
bool isLoading = false;
bool _isLoading = false;
dynamic empClientId;
dynamic empClientBranchId;
dynamic empHrId;
String? _postPreToken = '';
List<dynamic> reversedDataPolicy = [];
List<Map<String, dynamic>> originalData = []; // Original data source
List<Map<String, dynamic>> filteredData = []; // Filtered data source
dynamic argumentsData;
dynamic policyType;
dynamic policyName;
dynamic clientPolicyId;
dynamic clientId;
dynamic empRefId;
int inceptionType = 0;
TextEditingController searchController = TextEditingController();
late ApiService apiService;
int _currentPage = 1;
int _rowsPerPage = 5;
List<dynamic> get _paginatedData {
final startIndex = (_currentPage - 1) * _rowsPerPage;
final endIndex =
(_currentPage * _rowsPerPage).clamp(0, filteredData.length);
return filteredData.sublist(startIndex, endIndex);
}
@override
void initState() {
super.initState();
apiService = ApiService(context); // Initialize ApiService here
checkIds();
}
@override
void dispose() {
super.dispose();
}
Future<void> checkIds() async {
_postPreToken = await tokenService.getCurrentToken();
empClientId = await tokenService.readValue('empClientId');
// empClientBranchId = await tokenService.readValue('empClientBranchId');
empHrId = await tokenService.readValue('empHrId');
await getCDPoliciesDetails(empClientId, empHrId, _postPreToken);
}
Future<void> getCDPoliciesDetails(empClientId, empHrId, _postPreToken) async {
print('9');
setState(() {
isLoading = true;
});
try {
print('10');
final response = await apiService.getCDPoliciesToApi(empClientId, empHrId, _postPreToken);
if (response['status'] == 'success') {
setState(() {
isLoading = false;
});
setState(() {
getCDPolicies = List<Map<String, dynamic>>.from(response['data']);
originalData = getCDPolicies;
filteredData = List.from(originalData);
print('filteredData');
print(filteredData);
});
} else {
setState(() {
isLoading = false;
});
// ToastHelper.showWarningToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response['code']}');
}
} catch (e) {
setState(() {
isLoading = false;
});
print('Exception occurred: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
void search(String query) {
print(query);
// Check if the query is empty
if (query.isEmpty) {
// If search query is empty, show all data
setState(() {
filteredData = List.from(originalData);
});
} else {
// Filter the original data based on the search query
setState(() {
filteredData = originalData.where((row) {
// Implement your filter logic here
// For example, check if any field in the row contains the query
// Adjust this logic based on your data structure
return row['insurer_name']
.toString()
.toLowerCase()
.contains(query.toLowerCase()) ||
row['cd_master_account_no']
.toString()
.toLowerCase()
.contains(query.toLowerCase()) ||
row['balance']
.toString()
.toLowerCase()
.contains(query.toLowerCase());
}).toList();
});
}
print(filteredData.length);
}
void exportToCsv(List<Map<String, dynamic>> data) {
List<List<String>> rows = [];
// Header
rows.add(['Insurer Name', 'CD Account Number', 'Current Balance']);
// Data rows
for (var item in data) {
rows.add([
item['insurer_name'] ?? '',
item['cd_master_account_no'] ?? '',
'${item['balance'] ?? '0'}',
]);
}
// Convert to CSV string
String csvData = const ListToCsvConverter().convert(rows);
// For Web: Create download
final bytes = utf8.encode(csvData);
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.AnchorElement(href: url)
..setAttribute("download", "CD_Policies.csv")
..click();
html.Url.revokeObjectUrl(url);
handleExportAction();
}
Future<void> handleExportAction() async {
print('handleExportAction');
_postPreToken = await tokenService.getCurrentToken();
final postId = await tokenService.readValue('empHrId');
final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
var activity = "export_cddata";
print('postId - $postId');
print('preId - $preId');
print('activity - $activity');
try {
print('10');
final response = await apiService.getPostLogHrActivity(
postId!, preId!, _postPreToken!, activity);
if (response['status'] == 'success') {
print('Request success');
} else {
// ToastHelper.showWarningToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response['code']}');
}
} catch (e) {
print('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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10), // 👈 set your desired radius
),
// height: 400,
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// 🔙 Back + Title (LEFT)
Row(
children: [
IconButton(
onPressed: () => {},
icon: const Icon(
Icons.arrow_back_ios,
size: 18,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
const SizedBox(width: 6),
Text(
'CD',
style: GoogleFonts.poppins(
fontSize: 22,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
/// Push right content to end
const Spacer(),
/// 🔍 Search Box
Container(
width: 380,
height: 37,
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: searchController,
onChanged: search,
style: GoogleFonts.poppins(fontSize: 14),
decoration: const InputDecoration(
hintText: 'Search',
prefixIcon: Icon(Icons.search, size: 18),
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
),
const SizedBox(width: 12),
/// Export Button
SizedBox(
width: 116,
height: 37,
child: ElevatedButton(
onPressed: () => exportToCsv(filteredData),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
'Export',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
letterSpacing: 1,
),
),
),
),
],
),
SizedBox(height: 20),
isLoading
? Expanded(
// color: Color(0x98FFFCE5), // semi-transparent overlay
child: Center(
child: Image.asset(
'assets/nhance-loader.gif',
height: 60,
width: 60,
),
),
)
: Expanded(
child: _buildCDGrid(),
)
],
),
);
}
Widget _buildCDGrid() {
if (filteredData.isEmpty) {
return const Center(
child: Text('No CD Account Mapped'),
);
}
return GridView.builder(
itemCount: filteredData.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // desktop
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 3.8, // 🔥 matches image
),
itemBuilder: (context, index) {
return InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => cdTransactionDetails(
insurerName: filteredData[index]['insurer_name'],
cdMasterAccountNo: filteredData[index]['cd_master_account_no'],
insurerId: filteredData[index]['insurer_id'],
cd_ac_pk: filteredData[index]['cd_ac_pk'],
empClientId: empClientId,
),
),
);
},
child: _CDPolicyCard(data: filteredData[index]),
);
},
);
}
}
class _CDPolicyCard extends StatelessWidget {
final Map<String, dynamic> data;
const _CDPolicyCard({required this.data});
@override
Widget build(BuildContext context) {
final balance = double.tryParse(data['balance'].toString()) ?? 0;
final Color amountColor = balance < 0
? Colors.red
: balance < 50000
? Colors.orange
: Colors.green;
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFEAFAFA),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFA0D1D3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 7,
child: Text(
data['insurer_name'] ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF000000)
),
),
),
Expanded(
flex: 5,
child: Text(
"${balance.toStringAsFixed(0)}",
textAlign: TextAlign.right,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: amountColor,
),
),
)
],
),
/// Top row (amount + arrow)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
/// Account number
Text(
'CD Account Number: ${data['cd_master_account_no']}',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
),
),
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF009195),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(
Icons.open_in_new,
color: Colors.white,
size: 14,
),
)
],
),
],
),
);
}
}

View File

@ -8,14 +8,17 @@ import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:universal_html/html.dart' as html;
import 'package:collection/collection.dart';
import 'package:url_launcher/url_launcher.dart';
import 'customAppBar/customAppBar.dart';
import 'customAppBar/customFooter.dart';
import '../customAppBar/base_layout.dart';
import '../customAppBar/customAppBar.dart';
import '../customAppBar/customFooter.dart';
class cdTransactionDetails extends StatefulWidget {
final String insurerName;
@ -23,7 +26,7 @@ class cdTransactionDetails extends StatefulWidget {
final String insurerId;
final String cd_ac_pk;
final String empClientId;
final String postToken;
// final String postToken;
const cdTransactionDetails(
{Key? key,
required this.insurerName,
@ -31,15 +34,18 @@ class cdTransactionDetails extends StatefulWidget {
required this.insurerId,
required this.cd_ac_pk,
required this.empClientId,
required this.postToken});
// required this.postToken
});
@override
State<cdTransactionDetails> createState() => _cdTransactionDetailsState();
}
class _cdTransactionDetailsState extends State<cdTransactionDetails> {
final tokenService = TokenStorageService();
Uint8List? fileBytes;
List<Map<String, dynamic>> getCDTransData = [];
List<Map<String, dynamic>> getCDEndorsementData = [];
bool isLoading = false;
bool _isLoading = false;
// dynamic clintID;
@ -47,7 +53,9 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
// List<dynamic> dataPolicy = [];
List<dynamic> reversedDataPolicy = [];
List<Map<String, dynamic>> originalData = []; // Original data source
List<Map<String, dynamic>> originalEndorsementData = []; // Original data source
List<Map<String, dynamic>> filteredData = []; // Filtered data source
List<Map<String, dynamic>> filteredEndorsementData = []; // Filtered data source
List<Map<String, dynamic>> getCDTransDataAmount = []; // Filtered data source
dynamic argumentsData;
dynamic policyType;
@ -84,6 +92,9 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
super.dispose();
}
// downloadPolicyFiles?file_id=13
// getPolicyAndEndorsementFiles?cd_ac_pk=12
Future<void> getCdTransactionDetails() async {
print('9');
setState(() {
@ -91,8 +102,9 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
});
try {
print('10');
final _postPreToken = await tokenService.getCurrentToken();
final response = await apiService.getCdTransactionData(widget.empClientId,
widget.insurerId, widget.cd_ac_pk, widget.postToken);
widget.insurerId, widget.cd_ac_pk, _postPreToken!);
if (response['status'] == 'success') {
setState(() {
isLoading = false;
@ -130,6 +142,148 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
}
}
Future<void> _openEndorsementFile(id) async {
print('9');
try {
print('10');
final _postPreToken = await tokenService.getCurrentToken();
final response = await apiService.getOpenEndorsementFileData(id, _postPreToken!);
if (response['status'] == false) {
ToastHelper.showErrorToast(context, response['message']);
} else {
// ToastHelper.showWarningToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response['code']}');
}
} catch (e) {
print('Exception occurred: $e');
}
}
Future<void> getCdEndorsementDetails(id) async {
print('9');
setState(() {
isLoading = true;
});
try {
print('10');
final _postPreToken = await tokenService.getCurrentToken();
final response = await apiService.getCdEndorsementData(id, _postPreToken!);
if (response['status'] == true) {
setState(() {
isLoading = false;
});
setState(() {
getCDEndorsementData = List<Map<String, dynamic>>.from(response['data']);
originalEndorsementData = getCDEndorsementData;
filteredEndorsementData = List.from(originalEndorsementData);
_showFileListPopup(filteredEndorsementData);
print('filteredData');
print(filteredData);
});
} else {
setState(() {
isLoading = false;
});
getCDEndorsementData = List<Map<String, dynamic>>.from(response['data']);
if (getCDEndorsementData == null || getCDEndorsementData.isEmpty || getCDEndorsementData == null || (getCDEndorsementData as List).isEmpty) {
_showEmptyPopup(response['message']);
return;
}
// ToastHelper.showWarningToast(
// context, 'Request failed with status: ${response.statusCode}');
print('Request failed with status: ${response['code']}');
}
} catch (e) {
setState(() {
isLoading = false;
});
print('Exception occurred: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _showFileListPopup(List<Map<String, dynamic>> files) {
showDialog(
context: context,
barrierDismissible: true,
builder: (_) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text(
'Files',
style: TextStyle(fontWeight: FontWeight.w600),
),
content: SizedBox(
width: 400,
child: ListView.separated(
shrinkWrap: true,
itemCount: files.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) {
final file = files[index];
return ListTile(
leading: const Icon(
Icons.picture_as_pdf_outlined,
color: Color(0xFF00999E),
),
title: Text(
file['file_name'] ?? 'Document',
style: const TextStyle(fontSize: 14),
),
onTap: () {
// Navigator.pop(context); // close popup
_openEndorsementFile(file['id']);
},
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
);
},
);
}
void _showEmptyPopup(String message) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text(
'Message',
style: TextStyle(fontWeight: FontWeight.w600),
),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
);
},
);
}
void search(String query) {
print(query);
// Check if the query is empty
@ -230,7 +384,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
? '${item['policy_type']} - ${item['policy_no']}'
: '-',
item['endorsement_no'] ?? '',
item['sub_type'] ?? '',
item['sub_type_text'] ?? '',
item['transaction_type'] == 'Credit' ? '${item['amount']}' : '-',
item['transaction_type'] == 'Debit' ? '${item['amount']}' : '-',
'${item['balance'] ?? '0'}',
@ -256,9 +410,9 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
Future<void> handleExportAction() async {
print('handleExportAction');
final SharedPreferences prefs = await SharedPreferences.getInstance();
final postId = prefs.getString('empHrId');
final preId = prefs.getString('enrollmentEmpPrimaryId');
final postId = await tokenService.readValue('empHrId');
final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
var activity = "export_cdsummary";
print('postId - $postId');
@ -267,8 +421,9 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
try {
print('10');
final _postPreToken = await tokenService.getCurrentToken();
final response = await apiService.getPostLogHrActivity(
postId!, preId!, widget.postToken, activity);
postId!, preId!, _postPreToken!, activity);
if (response['status'] == 'success') {
print('Request success');
} else {
@ -305,182 +460,202 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
}
}
Future<void> _launchURL(String url) async {
final Uri uri = Uri.parse(url); // Parse the URL properly
print('_launchURL $uri');
if (uri != null) {
print('If $uri');
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
ToastHelper.showWarningToast(context, 'File not generated');
print('else $uri');
throw 'Could not launch $url';
}
}
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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: CustomAppBar(),
backgroundColor: Color(0xFFEFF3F6),
body: Stack(children: [
SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
top: 30, bottom: 200, left: 50, right: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
return isLoading ? Container(
color: Color(0x98FFFCE5), // 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
),
) : Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// 🔙 Back + Title (LEFT)
Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.arrow_back_ios,
size: 18,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
const SizedBox(width: 6),
Text(
'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
/// Push right content to end
const Spacer(),
/// 🔍 Search Box
Container(
width: 380,
height: 37,
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: searchController,
onChanged: search,
style: GoogleFonts.poppins(fontSize: 14),
decoration: const InputDecoration(
hintText: 'Search',
prefixIcon: Icon(Icons.search, size: 18),
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
),
const SizedBox(width: 12),
/// Export Button
SizedBox(
width: 116,
height: 37,
child: ElevatedButton(
onPressed: () => exportToCsv(filteredData),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
'Export',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
letterSpacing: 1,
),
),
),
),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildInfoCard('Deposits', '$total_deposit',
Color(0xFF39D45B), Icons.arrow_upward),
_buildInfoCard('Consumed', '$total_consumed',
Color(0xFFED1D24), Icons.arrow_downward),
_buildInfoCard('Refund', '$total_refund',
Color(0xFF39D45B), Icons.arrow_upward),
_buildInfoCard('Current Balance', '$currect_balance',
Colors.black, null),
],
),
SizedBox(height: 16),
Container(
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(
10), // 👈 set your desired radius
),
// height: 400,
// padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
MouseRegion(
cursor: SystemMouseCursors
.click, // 👈 shows pointer on hover
child: GestureDetector(
onTap: () {
Navigator.pop(context); // or your desired action
},
child: Row(
children: [
Icon(Icons.arrow_back_ios_new_outlined,
color: Color(0xFF707070)),
SizedBox(width: 8),
Expanded(
child: Text(
'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF101010),
),
),
),
],
),
),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildInfoCard('Deposits', '$total_deposit',
Color(0xFF39D45B), Icons.arrow_upward),
_buildInfoCard('Consumed', '$total_consumed',
Color(0xFFED1D24), Icons.arrow_downward),
_buildInfoCard('Refund', '$total_refund',
Color(0xFF39D45B), Icons.arrow_upward),
_buildInfoCard('Current Balance', '$currect_balance',
Colors.black, null),
],
),
SizedBox(height: 16),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(
10), // 👈 set your desired radius
),
// height: 400,
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 4,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: 400,
height: 37,
decoration: BoxDecoration(
color: Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
decoration: InputDecoration(
hintText: 'Search',
prefixIcon:
Icon(Icons.search, size: 18),
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
border: InputBorder
.none, // No border since Container handles it
),
controller: searchController,
onChanged: search,
style:
GoogleFonts.poppins(fontSize: 14),
),
),
),
),
SizedBox(width: 12),
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: 116,
height: 37,
child: ElevatedButton(
onPressed: () {
exportToCsv(filteredData);
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFE26728),
padding: EdgeInsets.all(
10), // Internal padding
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
10), // Border radius
side: BorderSide(
color: Colors
.transparent, // Optional border color
width: 1, // Border width
),
),
elevation: 0,
),
child: Text(
'Export',
style: GoogleFonts.poppins(
fontSize: 14,
color: Color(0xFFFFFFFF),
fontWeight: FontWeight.w700,
letterSpacing: 1),
),
),
),
],
),
),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(
child: SingleChildScrollView(
child: _buildCDDataTable(context),
),
)
],
),
],
Expanded(
child: SingleChildScrollView(
child: _buildCDDataTable(context),
),
)
],
))),
if (isLoading)
Container(
color: Color(0x98FFFCE5), // 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
),
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity, // Make the footer full width
child: CustomFooter(),
),
),
]));
)
],
));
}
Widget _buildInfoCard(
String label, String value, Color iconColor, IconData? icon) {
return Expanded(
@ -548,7 +723,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
// Header row
Container(
decoration: BoxDecoration(
color: Color(0xFFD7E9EB),
color: Color(0xFFD7E9EB),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
@ -675,6 +850,19 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Action',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 12,
color: const Color(0xFF000000),
fontWeight: FontWeight.bold,
),
),
),
],
),
),
@ -688,11 +876,11 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
decoration: BoxDecoration(
// color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white,
color: Colors.white,
borderRadius: BorderRadius.circular(6),
// color: Colors.white,
// borderRadius: BorderRadius.circular(6),
border: Border(
bottom: BorderSide(
color: Color(0xFFD7E9EB), // 👈 Bottom border color
color: Color(0xFFA9D9DE), // 👈 Bottom border color
width: 1, // 👈 Optional: thickness
),
),
@ -765,7 +953,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
Expanded(
flex: 2,
child: Text(
item['sub_type'] ?? '-',
item['sub_type_text'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
@ -832,6 +1020,35 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
),
),
),
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_ActionIconButton(
icon: Icons.picture_as_pdf_outlined,
onTap: () {
getCdEndorsementDetails(item['id']);
},
subType: item['sub_type']
),
const SizedBox(width: 8),
_ActionIconButton(
icon: Icons.folder_open_outlined,
onTap: () {
if (item['split_up_url'] != null && item['split_up_url'].toString().trim().isNotEmpty){
_launchURL(item['split_up_url']);
} else{
ToastHelper.showWarningToast(context, 'File not generated');
}
},
subType: item['sub_type']
),
],
),
),
],
),
);
@ -917,6 +1134,41 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
}
}
class _ActionIconButton extends StatelessWidget {
final IconData icon;
final VoidCallback onTap;
final String? subType;
const _ActionIconButton({
required this.icon,
required this.onTap,
required this.subType,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 36,
height: 36,
child: Material(
color: const Color(0xFFDFF4F5), // light teal bg
borderRadius: BorderRadius.circular(10),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: (subType == '3' || subType == '4') ? onTap : null,
child: Icon(
icon,
size: 22,
color: Colors.black,
),
),
),
);
}
}
// Sample Data class representing each element in the array
class Data {
final dynamic value;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,767 @@
import 'dart:ui';
import 'package:csv/csv.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:nhancepolicy/presentation/preFileUpload.dart';
import 'package:nhancepolicy/presentation/postFileUpload.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'dart:convert';
import 'dart:async';
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:universal_html/html.dart' as html;
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:url_launcher/url_launcher.dart';
import '../customAppBar/base_layout.dart';
class excelErrorScreen extends StatefulWidget {
final String ClientId;
final String policy_no;
final String action;
final String created_at;
final String clientBranchId;
final String Token;
final String TokenType;
final String id;
const excelErrorScreen({
Key? key,
required this.ClientId,
required this.policy_no,
required this.action,
required this.created_at,
required this.clientBranchId,
required this.Token,
required this.TokenType,
required this.id,
}) : super(key: key);
@override
State<excelErrorScreen> createState() => _activePolicyExcelErrorState();
}
class _activePolicyExcelErrorState extends State<excelErrorScreen>
with TickerProviderStateMixin {
final tokenService = TokenStorageService();
bool isLoading = false;
dynamic empPrimaryId;
dynamic empClientId;
dynamic empClientBranchId;
dynamic empHrId;
dynamic enrollmentClient_id;
dynamic enrollmentEmpClientBranchId;
dynamic enrollmentHrId;
TextEditingController searchController = TextEditingController();
List<String> excelHeader = [];
List<List<Map<String, dynamic>>> excelData = [];
late int excelValidationStaus = 1;
bool isSuccess = false;
String successContent = '';
List<List<Map<String, dynamic>>> filteredExcelData = [];
String? _postPreToken = '';
late ApiService apiService;
int _currentPage = 1;
int _rowsPerPage = 5;
List<List<Map<String, dynamic>>> get _paginatedExcelData {
final start = (_currentPage - 1) * _rowsPerPage;
final end =
(_currentPage * _rowsPerPage).clamp(0, filteredExcelData.length);
return filteredExcelData.sublist(start, end);
}
final ScrollController _verticalController = ScrollController();
final ScrollController _horizontalController = ScrollController();
@override
void initState() {
super.initState();
apiService = ApiService(context);
getCDPoliciesDetails();
}
@override
void dispose() {
_verticalController.dispose();
_horizontalController.dispose();
super.dispose();
}
Future<void> getCDPoliciesDetails() async {
setState(() {
isLoading = true;
});
try {
final response =
await apiService.getExcelFileErrorsApi(widget.id, widget.TokenType);
// 🔴 CASE 1: Empty data popup + back
if (response['data'] is List && response['data'].isEmpty) {
setState(() => isLoading = false);
_showEmptyDataDialog(response['message']);
return;
}
// 🟢 CASE 2: Success with data
if (response['status'] == true) {
ToastHelper.showSuccessToast(context, response['message']);
setState(() {
isLoading = false;
isSuccess = false;
excelValidationStaus = 1;
excelHeader =
List<String>.from(response['data']['excel_header']);
excelData = (response['data']['excel_data'] as List)
.map<List<Map<String, dynamic>>>(
(row) => row
.map<Map<String, dynamic>>(
(cell) => Map<String, dynamic>.from(cell))
.toList(),
)
.toList();
filteredExcelData = List.from(excelData);
});
}
// 🟡 CASE 3: API failed with message
else {
setState(() => isLoading = false);
_showEmptyDataDialog(response['message']);
}
} catch (e) {
setState(() => isLoading = false);
print('Exception occurred: $e');
_showEmptyDataDialog('Something went wrong. Please try again.');
}
}
void _showEmptyDataDialog(String message) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
title: const Text(
'Message',
style: TextStyle(fontWeight: FontWeight.w600),
),
content: Text(message),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // close dialog
Navigator.of(context).pop(); // go back page
},
child: const Text('Close'),
),
],
);
},
);
}
void search(String query) {
if (query.isEmpty) {
setState(() {
filteredExcelData = List.from(excelData);
_currentPage = 1;
});
return;
}
final lowerQuery = query.toLowerCase();
setState(() {
filteredExcelData = excelData.where((row) {
return row.any((cell) {
final value = cell['value'];
return value != null &&
value.toString().toLowerCase().contains(lowerQuery);
});
}).toList();
_currentPage = 1;
});
}
// emp_is_active
void exportToCsv({
required List<String> excelHeader,
required List<List<Map<String, dynamic>>> excelData,
}) {
List<List<String>> rows = [];
/// 1 Add headers
rows.add(excelHeader);
/// 2 Add rows
for (final row in excelData) {
rows.add(
row.map<String>((cell) {
final value = cell['value'];
return value == null ? '' : value.toString();
}).toList(),
);
}
/// 3 Convert to CSV
final csvData = const ListToCsvConverter().convert(rows);
/// 4 Download (Flutter Web)
final bytes = utf8.encode(csvData);
final blob = html.Blob([bytes], 'text/csv');
final url = html.Url.createObjectUrlFromBlob(blob);
html.AnchorElement(href: url)
..setAttribute("download", "Excel_Error_File.csv")
..click();
html.Url.revokeObjectUrl(url);
}
// Future<void> handleExportAction() async {
// print('handleExportAction');
//
// final postId = await tokenService.readValue('empHrId');
// final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
//
// var activity = "export_empdata";
//
// var activityPre = "export_preempdata";
// dynamic response;
//
// print('postId - $postId');
// print('preId - $preId');
// print('activity - $activity');
//
// try {
// print('10');
//
// if (widget.TokenType == 'pre') {
// response = await apiService.getPreLogHrActivity(
// postId!, preId!, widget.Token, activityPre);
// } else if (widget.TokenType == 'post') {
// response = await apiService.getPostLogHrActivity(
// postId!, preId!, widget.Token, activity);
// }
//
// if (response['status'] == 'success') {
// print('Request success');
// } else {
// // ToastHelper.showWarningToast(
// // context, 'Request failed with status: ${response.statusCode}');
// print('Request failed with status: ${response['code']}');
// }
// } catch (e) {
// print('Exception occurred: $e');
// }
// }
String _capitalize(String? value) {
if (value == null || value.isEmpty) return '';
return value[0].toUpperCase() + value.substring(1).toLowerCase();
}
String formatDateTime(String dateTime) {
final parsedDate = DateTime.parse(dateTime);
return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate);
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: _buildContent(context),
);
}
Widget _buildContent(BuildContext context) {
return isLoading
? Container(
color: Color(0x98FFFCE5), // 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
),
)
: Container(
// padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// 🔙 Back + Title (LEFT)
Row(
children: [
IconButton(
onPressed: () => {Navigator.pop(context)},
icon: const Icon(
Icons.arrow_back_ios,
size: 18,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
const SizedBox(width: 6),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.policy_no ?? '',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
if (widget.TokenType != 'pre')
RichText(
text: TextSpan(
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
),
children: [
TextSpan(
text: widget.action,
style: const TextStyle(
color: Color(0xFF00999E),
),
),
const TextSpan(
text: ' - ',
style: TextStyle(
color: Color(0xFF585858),
),
),
TextSpan(
text: formatDateTime(widget.created_at),
style: const TextStyle(
color: Color(0xFF585858),
),
),
],
),
),
],
),
),
],
),
/// Push right content to end
const Spacer(),
/// 🔍 Search Box
Container(
width: 380,
height: 37,
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: searchController,
onChanged: search,
style: GoogleFonts.poppins(fontSize: 14),
decoration: const InputDecoration(
hintText: 'Search',
prefixIcon: Icon(Icons.search, size: 18),
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
),
const SizedBox(width: 12),
/// Export Button
SizedBox(
width: 116,
height: 37,
child: ElevatedButton(
onPressed: () {
exportToCsv(
excelHeader: excelHeader,
excelData: filteredExcelData, // or excelData
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
'Export',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
letterSpacing: 1,
),
),
),
),
],
),
SizedBox(height: 20),
Expanded(
child: _buildCDDataTable(context),
),
],
),
);
}
Widget _buildCDDataTable(BuildContext context) {
return ScrollConfiguration(
behavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
},
),
child: _buildScrollableTable(context),
);
}
Widget _buildScrollableTable(BuildContext context) {
const double columnWidth = 160;
final double tableWidth = excelHeader.length * columnWidth;
return Scrollbar(
thumbVisibility: true,
controller: _verticalController,
child: SingleChildScrollView(
controller: _verticalController,
physics: const ClampingScrollPhysics(), // 👈 mouse wheel
scrollDirection: Axis.vertical,
child: Scrollbar(
thumbVisibility: true,
controller: _horizontalController,
notificationPredicate: (n) => n.depth == 1,
child: SingleChildScrollView(
controller: _horizontalController,
physics: const ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
child: SizedBox(
width: tableWidth,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: const Color(0xFFD7E9EB),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: excelHeader.map((header) {
return SizedBox(
width: columnWidth,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12),
child: Text(
header,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
);
}).toList(),
),
),
const SizedBox(height: 6),
/// ROWS
..._paginatedExcelData.map((row) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFFA9D9DE),
width: 1,
),
),
),
child: Row(
children: row.map((cell) {
final bool hasError = cell.containsKey('error');
return SizedBox(
width: columnWidth,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: hasError
? Row(
children: [
Expanded(
child: Text(
cell['value']?.toString() ?? '-',
style: GoogleFonts.poppins(
fontSize: 12,
),
),
),
const SizedBox(width: 6),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
icon: const Icon(
Icons.error_outline,
color: Colors.red,
size: 16,
),
onPressed: () {
showDialog(
context: context,
barrierDismissible: true,
builder: (_) {
final List errors = cell['error'] as List;
return Dialog(
backgroundColor: Colors.transparent,
child: Container(
width: 420,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
/// ERROR TITLE
Text(
'Error!',
style: GoogleFonts.poppins(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 16),
/// RED ICON
Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
color: Color(0xFFE0002A),
shape: BoxShape.circle,
),
child: const Center(
child: Text(
'!',
style: TextStyle(
color: Colors.white,
fontSize: 36,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 20),
/// ERROR HEADING (optional first error)
Text(
errors.isNotEmpty ? errors.first.toString() : 'Validation Error',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
const SizedBox(height: 12),
/// ERROR DETAILS
...errors.skip(1).map(
(e) => Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
e.toString(),
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 14,
color: const Color(0xFFE09B2D), // orange text
),
),
),
),
const SizedBox(height: 20),
/// OK BUTTON
SizedBox(
width: 120,
height: 30,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE0002A),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 0,
),
child: Text(
'OK',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
],
),
),
);
},
);
},
),
],
)
: Text(
cell['value']?.toString() ?? '-',
style: GoogleFonts.poppins(
fontSize: 12,
),
),
),
);
}).toList(),
),
);
}).toList(),
/// PAGINATION
SizedBox(
width: tableWidth,
child: _buildPagination(context),
),
],
),
),
),
),
),
);
}
Widget _buildPagination(BuildContext context) {
final totalPages =
(filteredExcelData.length / _rowsPerPage).ceil();
if (totalPages <= 1) {
return const SizedBox.shrink(); // 👈 hide if only one page
}
return Row(
mainAxisAlignment: MainAxisAlignment.end, // 👈 right aligned
children: [
DropdownButton<int>(
value: _rowsPerPage,
items: [5, 10, 15, 20, 50].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(
' $value ',
style: GoogleFonts.poppins(fontSize: 14),
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_rowsPerPage = newValue!;
_currentPage = 1;
});
},
),
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: _currentPage > 1
? () => setState(() => _currentPage--)
: null,
),
for (int i = 1; i <= totalPages; i++)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: _currentPage == i
? const Color(0xFF00A6A6)
: Colors.grey[300],
foregroundColor:
_currentPage == i ? Colors.white : Colors.black,
minimumSize: const Size(36, 36),
padding: EdgeInsets.zero,
),
onPressed: () {
setState(() {
_currentPage = i;
});
},
child: Text(i.toString()),
),
),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++)
: null,
),
],
);
}
}

490
lib/presentation/hrDashboard.dart Executable file
View File

@ -0,0 +1,490 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'dart:ui_web' as ui;
import 'package:universal_html/html.dart' as html;
import '../config/environment.dart';
import '../customAppBar/base_layout.dart';
class hrDashboard extends StatefulWidget {
hrDashboard({Key? key,}) : super(key: key);
@override
State<hrDashboard> createState() => _hrDashboardState();
}
class _hrDashboardState extends State<hrDashboard>
with SingleTickerProviderStateMixin {
late ApiService apiService;
bool isLoading = false;
String? selectedPolicyId;
List<Map<String, dynamic>> activePoliciesList = [];
String? _metabaseToken;
String? _metabaseUrl;
bool isPolicyLoading = false;
bool isDashboardLoading = false;
bool hasDashboardError = false;
bool _metabaseLoaded = false; // FIX
List<int> postModules = [];
dynamic policy_name;
dynamic getPreCardArrays = [];
dynamic getPostCardArrays = [];
String? _postPreToken = '';
dynamic empClientBranchId;
dynamic empHrId;
dynamic empClientId;
int stausVal = 1;
String _dashboardViewType = '';
bool isDropdownOpen = false;
final FocusNode _policyFocusNode = FocusNode();
final tokenService = TokenStorageService();
@override
void initState() {
super.initState();
apiService = ApiService(context);
_policyFocusNode.addListener(() {
if (!_policyFocusNode.hasFocus) {
setState(() => isDropdownOpen = false);
}
});
_loadToken();
}
@override
void dispose() {
_policyFocusNode.dispose();
super.dispose();
}
Future<void> _loadToken() async {
_postPreToken = await tokenService.getCurrentToken();
final postRaw = await tokenService.readValue('empAllowed_modules');
postModules = postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: [];
if (!(postModules.contains(2) ||
postModules.contains(3) ||
postModules.contains(4))) {
// No dashboard permission
setState(() {
hasDashboardError = true;
});
return;
}
empClientId = await tokenService.readValue('empClientId');
empClientBranchId = await tokenService.readValue('empClientBranchId');
empHrId = await tokenService.readValue('empHrId');
await getPostCashDepositDetails(
empClientBranchId, empClientId, empHrId, _postPreToken);
}
Future<void> getPostCashDepositDetails(
empClientBranchId, empClientId, empHrId, _postPreToken) async {
print('IN');
print("clintBranchId -$empClientBranchId");
print("clintID -$empClientId");
print("hr_id -$empHrId");
print("token -$_postPreToken");
isLoading = true;
// 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);
print('IN1');
if (response['status'] == 'success') {
final list = List<Map<String, dynamic>>.from(response['data']);
setState(() {
activePoliciesList = list;
});
// AUTO SELECT FIRST POLICY HERE
if (list.isNotEmpty) {
selectedPolicyId = list.first['client_policy_id'].toString();
await _loadDashboardByPolicy(selectedPolicyId!);
} else {
setState(() {
hasDashboardError = true;
});
}
} else {
isLoading = false;
print('API request failed with status');
setState(() {
activePoliciesList = [];
});
print('API request failed with status');
}
} catch (e) {
print('Exception occurred: $e');
}
}
Future<void> _loadMetabaseDashboard() async {
try {
final response = await http.get(
Uri.parse(
'${Environment.apiUrlPost.replaceAll("employeeRest/", "")}metaDashboardDemo?api=1',
),
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
_metabaseToken = json['data']['metabaseToken'];
_metabaseUrl = json['data']['metabaseUrl'];
final htmlContent = _buildMetabaseHtml(
token: _metabaseToken!,
url: _metabaseUrl!,
);
final iframe = html.IFrameElement()
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..style.minHeight = '100vh'
..srcdoc = htmlContent;
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'metabase-dashboard',
(int viewId) => iframe,
);
setState(() {
_metabaseLoaded = true;
});
} else {
throw Exception('Failed to load Metabase');
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Metabase loading failed');
debugPrint(e.toString());
}
}
Future<void> _loadDashboardByPolicy(String clientPolicyId) async {
try {
setState(() {
isDashboardLoading = true;
_metabaseLoaded = false;
});
final response = await apiService.postHrDashboard({
"client_id": empClientId,
"client_policy_id": clientPolicyId,
}, _postPreToken);
if (response['status'] == 'success') {
_registerMetabaseIframe(
token: response['data']['metabaseToken'],
url: response['data']['metabaseUrl'],
clientPolicyId: clientPolicyId,
);
setState(() {
_metabaseLoaded = true;
});
} else {
ToastHelper.showErrorToast(context, response['message']);
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Dashboard loading failed');
} finally {
setState(() {
isDashboardLoading = false;
});
}
}
void _registerMetabaseIframe({
required String token,
required String url,
required String clientPolicyId,
}) {
_dashboardViewType = 'metabase-dashboard-$clientPolicyId';
final htmlContent = _buildMetabaseHtml(
token: token,
url: url,
);
final iframe = html.IFrameElement()
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..style.minHeight = '100vh'
..srcdoc = htmlContent;
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
_dashboardViewType,
(int viewId) => iframe,
);
}
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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: _buildContent(context),
);
}
Widget _buildContent(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await tokenService.clearAll();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: Scaffold(
body: Expanded(
child: isDashboardLoading
? Center(
child: Image.asset(
'assets/nhance-loader.gif',
height: 60,
width: 60,
),
)
: _metabaseLoaded
? (postModules.isNotEmpty && activePoliciesList.isEmpty) ?
Padding(
padding: EdgeInsets.all(16),
child: Text(
'No dashboard data available for your account',
style: TextStyle(color: Colors.grey),
),
) : Container(
decoration: BoxDecoration(
// color: Colors.red.shade50,
// color: Colors.white,
borderRadius: BorderRadius.circular(10), // 👈 set your desired radius
),
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 🔹 TOP BAR (always clickable)
Material(
elevation: 2,
color: Colors.white,
child: _buildPolicySelector(),
),
SizedBox(height: 20),
// 🔹 DASHBOARD AREA (isolated)
Expanded(
child: Stack(
children: [
Positioned.fill(
child: _buildDashboard(),
),
],
),
),
],
)
)
: const Center(
child: Text(
'No dashboard data available',
style: TextStyle(color: Colors.grey),
),
),
),
),
);
}
Widget _buildPolicySelector() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Row(
children: [
const Text(
'Select Policy',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(width: 12),
SizedBox(
width: 420,
child: DropdownButtonFormField<String>(
focusNode: _policyFocusNode,
value: selectedPolicyId,
isExpanded: true,
items: activePoliciesList.map((policy) {
return DropdownMenuItem<String>(
value: policy['client_policy_id'].toString(),
child: Text(
'${policy['type']} - ${policy['policy_no']}',
overflow: TextOverflow.ellipsis,
),
);
}).toList(),
onTap: () {
setState(() => isDropdownOpen = true);
},
onChanged: isDashboardLoading
? null
: (value) {
if (value == selectedPolicyId) return;
setState(() {
selectedPolicyId = value!;
});
_loadDashboardByPolicy(value!);
},
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
);
}
Widget _buildDashboard() {
if (isDashboardLoading) {
return Center(
child: Image.asset(
'assets/nhance-loader.gif',
height: 60,
width: 60,
),
);
}
if (!_metabaseLoaded || _dashboardViewType.isEmpty) {
return const Center(
child: Text(
'No dashboard data available',
style: TextStyle(color: Colors.grey),
),
);
}
return HtmlElementView(viewType: _dashboardViewType);
}
String _buildMetabaseHtml({
required String token,
required String url,
}) {
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Metabase Dashboard</title>
<script defer src="$url/app/embed.js"></script>
<script>
window.metabaseConfig = {
theme: { preset: "light" },
isGuest: true,
instanceUrl: "$url"
};
</script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<metabase-dashboard
token="$token"
with-title="true"
with-downloads="true">
</metabase-dashboard>
</body>
</html>
''';
}
}

View File

@ -0,0 +1,987 @@
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/service/hrDashboardTabs/preEnrollment.dart';
import 'package:nhancepolicy/service/token_storage_service.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 'hrPolicyDetails.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();
print('token - $token');
print('token check in');
if ((token != null && token!.isNotEmpty)) {
print('token check done');
_loadToken();
} else {
print('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))
: [];
print('enrollmentModules $enrollmentModules');
print('postModules $postModules');
_postPreToken = await tokenService.getCurrentToken();
print(_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> getPreCashDepositDetails(enrollmentEmpClientBranchId,
enrollmentClient_id, enrollmentHrId, _postPreToken) async {
print('IN');
print("clintBranchId -$enrollmentEmpClientBranchId");
print("clintID -$enrollmentClient_id");
print("hr_id -$enrollmentHrId");
print("token -$_postPreToken");
isLoading = true;
// 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);
print('IN1');
if (response['status'] == 'success') {
isLoading = false;
setState(() {
print('response');
print(response['data']);
getPreCardArrays = List<Map<String, dynamic>>.from(response['data']);
print('getPreCardArrays');
print(getPreCardArrays);
openForEnrollmentList = getPreCardArrays;
});
print('IN2');
} else {
print('API request failed with status');
}
} catch (e) {
print('Exception occurred: $e');
}
}
Future<void> getPostCashDepositDetails(
empClientBranchId, empClientId, empHrId, _postPreToken) async {
print('IN');
print("clintBranchId -$empClientBranchId");
print("clintID -$empClientId");
print("hr_id -$empHrId");
print("token -$_postPreToken");
isLoading = true;
// 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);
print('IN1');
if (response['status'] == 'success') {
isLoading = false;
setState(() {
print('response');
print(response['data']);
getPostCardArrays = List<Map<String, dynamic>>.from(response['data']);
print('getPostCardArrays');
print(getPostCardArrays);
activePoliciesList = getPostCardArrays;
});
print('IN2');
} else {
isLoading = false;
print('API request failed with status');
setState(() {
activePoliciesList = [];
});
print('API request failed with status');
}
} catch (e) {
print('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: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
}
Future<void> getEcardBulkDownload(clientPolicyId) async {
try {
print('10');
empHrId = await tokenService.readValue('empHrId');
final response = await apiService.getEcardBulkDownloadApi(clientPolicyId,empHrId,'',_postPreToken!);
if (response['status'] == true) {
print('Request success');
_showBulkDownloadSuccessPopup(response['message']);
} else {
ToastHelper.showErrorToast(context, response['message']);
print('Request failed with status: ${response['code']}');
}
} catch (e) {
print('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: const TextStyle(
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: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: buildPoliciesBody(
openEnrollment: openForEnrollmentList,
activePolicies: activePoliciesList,
),
),
);
}
Widget buildPoliciesBody({
required List<Map<String, dynamic>> openEnrollment,
required List<Map<String, dynamic>> activePolicies,
}) {
return 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,
height: 300,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// ================= OPEN FOR ENROLLMENT =================
const Text(
'Open for Enrollment',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(height: 14),
Container(
width: double.infinity,
// padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(6),
// border: Border.all(color: Colors.black12),
),
child: openEnrollment.isEmpty
? _EmptyBox('No policies open for enrollment')
: _PolicyGrid(
policies: openEnrollment, isEnrollment: true),
),
const SizedBox(height: 24),
],
),
),
],
if(postModules.contains(2))...[
SizedBox(height: 20),
Container(
width: double.infinity,
height: 300,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// ================= ACTIVE POLICIES HEADER =================
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Active Policies',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600),
),
_ActiveExpiredToggle(
selectedIndex: selectedIndex,
onChange: (value) async {
setState(() {
selectedIndex = value;
stausVal = value == 1 ? 1 : 0;
});
empClientId =
await tokenService.readValue('empClientId');
empClientBranchId =
await tokenService.readValue('empClientBranchId');
empHrId = await tokenService.readValue('empHrId');
await getPostCashDepositDetails(
empClientBranchId,
empClientId,
empHrId,
_postPreToken,
);
},
),
],
),
const SizedBox(height: 14),
Container(
width: double.infinity,
// padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(6),
// border: Border.all(color: Colors.black12),
),
child: activePolicies.isEmpty
? _EmptyBox('No Active policies')
: _PolicyGrid(
policies: activePolicies,
isEnrollment: false,onBulkDownload: (clientPolicyId) {
getEcardBulkDownload(clientPolicyId);
}
),
),
const SizedBox(height: 10),
const Align(
alignment: Alignment.bottomRight,
child: Text(
'* Premium may vary subject to claims',
style: TextStyle(fontSize: 10, color: Colors.red),
),
),
],
),
),
]
],
),
)),
);
}
}
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(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // desktop
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: isEnrollment ? 2.8 : 2.2,
),
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;
}
print('$token , $empClientId, $empBranchId');
print(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
Text(
data['policy_no'] ?? '',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
/// Insurer
Text(
data['insurer_name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 4),
/// Closes On
Text(
'Closes on: ${data['policy_expiry_date'] ?? ''}',
style: const TextStyle(
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: 'Enrolled',
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;
const _ActivePolicyCardNew({required this.data, this.onTap,this.onBulkDownload,});
@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: [
/// 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: () {
print('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['policy_no'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 4),
/// INSURER
Text(
data['insurer_name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 4),
/// DATE RANGE
Text(
'${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}',
style: const TextStyle(
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: TextStyle(fontSize: 13, color: color),
),
const SizedBox(width: 8),
Text(
value.toString(),
style: TextStyle(
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: TextStyle(
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: const TextStyle(color: Colors.grey)),
),
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'dart:convert';
import '../config/environment.dart';
@ -8,6 +8,7 @@ import '../customAppBar/toastHelper.dart';
class ApiService {
final BuildContext context;
final tokenService = TokenStorageService();
String? _token;
String? _hrtoken;
bool _isSessionOutToastShown = false; // Flag to track toast message
@ -17,10 +18,7 @@ class ApiService {
}
Future<void> _initializeToken() async {
final prefs = await SharedPreferences.getInstance();
_token = prefs.getString('token') ?? '';
// final hrprefs = await SharedPreferences.getInstance();
// _hrtoken = hrprefs.getString('hrtoken') ?? '';
_token = tokenService.getCurrentToken() ?? '';
}
Future<void> getTokenLoadAPI(token) async {
@ -33,9 +31,9 @@ class ApiService {
if (_token == null) {
await _initializeToken();
}
final SharedPreferences prefs = await SharedPreferences.getInstance();
final empClientId = prefs.getString('empClientId');
final empClientBranchId = prefs.getString('empClientBranchId');
final empClientId = await tokenService.readValue('empClientId');
final empClientBranchId = await tokenService.readValue('empClientBranchId');
final url = Uri.parse(
'${Environment.apiUrl}getClientDetails?post_client_id=$empClientId&post_branch_id=$empClientBranchId&pre_client_id=$clientId&pre_branch_id=$branchID');
final headers = {
@ -45,6 +43,24 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getActiveAndInactivePolicyDetails(
String? clientId,
String? empCode,
String? status,
String? branchID,
String? mobileNo,String? emailId) async {
if (_token == null) {
await _initializeToken();
}
final url = Uri.parse(
'${Environment.apiUrl}getEmployeeActiveOrInactivePolicy?client_id=${clientId ?? ''}&emp_code=${empCode ?? ''}&type=${status ?? ''}&client_branch_id=${branchID ?? ''}&mobile_no=${mobileNo ?? ''}&email_id=${emailId ?? ''}');
final headers = {
'Authorization': 'Bearer $_token' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> getSelfEmployeeProfileToApi(
token,
String clientId,
@ -163,6 +179,26 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getExcelFileErrorsApi(id,type) async {
print(_token);
if (_token == null) {
await _initializeToken();
}
final apiURL;
if(type == 'post'){
apiURL = Environment.apiUrlPost;
} else {
apiURL = Environment.apiUrl;
}
final url = Uri.parse(
'${apiURL}getExcelFileErrors/${id}/api');
final headers = {
'Authorization': 'Bearer $_token' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> removeAddonsGmcDependentToAPI(
String empCodeString, String addOnsDependentClientPolicyId) async {
print(_token);
@ -478,7 +514,8 @@ class ApiService {
final url = Uri.parse('${Environment.apiUrlPost}logHrActivity');
final headers = {
'Authorization': 'Bearer $token' ?? '',
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final body = {
@ -503,6 +540,39 @@ class ApiService {
}
}
Future<Map<String, dynamic>> getEcardBulkDownloadApi(client_policy_id,empHrId,emp_policy_ids,String token) async {
// final url = Uri.parse(
// '${Environment.apiUrlPost}logHrActivity?user_id=$postId&pre_hr_id=$preId&user_type=hr&activity=$activity');
final url = Uri.parse('${Environment.apiUrlPost}bulkEcardDownloadAsZip');
final headers = {
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final body = {
'client_policy_id': client_policy_id,
'hr_id': empHrId,
'emp_policy_ids':emp_policy_ids,
};
final response = await http.post(
url,
headers: headers,
body: jsonEncode(body),
);
// final response = await _makeGetRequest(url, headers);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception(
'Failed to log HR activity: ${response.statusCode} ${response.body}');
}
}
Future<Map<String, dynamic>> getPostLogHrActivity(
String? postId, String? preId, String token, String activity) async {
print("getCashDepositDetailsToApi1");
@ -516,9 +586,10 @@ class ApiService {
final url = Uri.parse('${Environment.apiUrlPost}logHrActivity');
final headers = {
'Authorization': 'Bearer $token' ?? '',
};
final headers = {
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final body = {
'user_id': postId,
@ -553,7 +624,8 @@ class ApiService {
final url = Uri.parse('${Environment.apiUrl}logHrActivity');
final headers = {
'Authorization': 'Bearer $token' ?? '',
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final body = {
@ -578,6 +650,32 @@ class ApiService {
}
}
Future<Map<String, dynamic>> postHrDashboard(params,token) async {
final url = Uri.parse('${Environment.apiUrlPost}getHrDashboad');
final headers = {
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final body = params;
final response = await http.post(
url,
headers: headers,
body: jsonEncode(body),
);
// final response = await _makeGetRequest(url, headers);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception(
'Failed to log HR activity: ${response.statusCode} ${response.body}');
}
}
Future<Map<String, dynamic>> getClaimPoliciesToApi(String token) async {
print("getgetClaimPoliciesToApii1");
final url = Uri.parse('${Environment.apiUrlPost}claimsSearch');
@ -589,6 +687,16 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getClaimPoliciesFileDownload(id,String token) async {
final url = Uri.parse('${Environment.apiUrlPost}hrFileDownload?id=$id');
final headers = {
'Authorization': 'Bearer $token' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> getClaimPoliciesListDataToApi(
String token, Map<String, dynamic> body) async {
print("getgetClaimPoliciesToApii1");
@ -638,6 +746,30 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getOpenEndorsementFileData(id, String token) async {
print("getPolicyAndEndorsementFiles");
final url = Uri.parse(
'${Environment.apiUrlPost}downloadPolicyFiles?file_id=$id');
final headers = {
'Authorization': 'Bearer $token' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> getCdEndorsementData(id, String token) async {
print("getPolicyAndEndorsementFiles");
final url = Uri.parse(
'${Environment.apiUrlPost}getPolicyAndEndorsementFiles?cd_ac_pk=$id');
final headers = {
'Authorization': 'Bearer $token' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> getClaimsHistoryToApi(
String ticket_type_id, String token) async {
print("getCashDepositDetailsToApi1");
@ -663,7 +795,7 @@ class ApiService {
}
Future<Map<String, dynamic>> getEmployeeAndDependenceToApi(
String clintID, String getPolicyNo, String empRefId, String token) async {
String clintID, getPolicyNo, String empRefId, String token) async {
print(_hrtoken);
if (token == null) {
await _initializeToken();
@ -691,15 +823,22 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getFileListToApi(empPrimaryId,cardPolicyNo,String token) async {
Future<Map<String, dynamic>> getFileListToApi(empPrimaryId,cardPolicyNo,empClientId,String token,String type) async {
print(_hrtoken);
if (token == null) {
await _initializeToken();
}
final apiURL;
if(type == 'post'){
apiURL = Environment.apiUrlPost;
} else {
apiURL = Environment.apiUrl;
}
final url = Uri.parse(
'${Environment.apiUrlPost}hrFileList?created_by=$empPrimaryId&policy_no=$cardPolicyNo');
'${apiURL}hrFileList?created_by=$empPrimaryId&policy_no=$cardPolicyNo&client_id=$empClientId');
final headers = {
'Authorization': 'Bearer $token' ?? '',
'Authorization': 'Bearer ${token ?? ''}',
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
};
final response = await _makeGetRequest(url, headers);
return response;
@ -815,8 +954,10 @@ class ApiService {
// Clear only mobile storage
// if (!isWeb) {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
await tokenService.clearAll();
print('Secure Storage Cleared');
// }
if (context.mounted) {
print('context.mounted');
@ -860,16 +1001,26 @@ class ApiService {
await _clearLocalStorageAndRedirect();
}
return {};
} else if (response.statusCode == 429) {
final body = jsonDecode(response.body);
final message = body['message'];
ToastHelper.showWarningToast(context, message);
return {};
} else {
throw Exception('Failed to load data');
}
}
Future<void> _clearLocalStorageAndRedirect() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
// Assuming you have access to the context
await tokenService.clearAll(); // 🔐 clears flutter_secure_storage
ToastHelper.showErrorToast(context, 'Session Out');
Navigator.pushNamed(context, 'hrLogin');
if (!context.mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
}

View File

@ -0,0 +1,70 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/widgets.dart';
class UploadedFile {
final PlatformFile file;
final TextEditingController controller;
UploadedFile({required this.file})
: controller = TextEditingController(
text: file.extension != null
? file.name.replaceAll('.${file.extension}', '')
: file.name);
String get label => controller.text;
void dispose() => controller.dispose();
}
class FileUploadService {
FileUploadService._();
static final FileUploadService _instance = FileUploadService._();
factory FileUploadService() => _instance;
final List<UploadedFile> _files = [];
// Expose as unmodifiable but containing UploadedFile objects
List<UploadedFile> get files => List.unmodifiable(_files);
// Allowed extensions
static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg', 'heic'];
Future<String?> pickFiles({int maxFileSizeInMB = 3}) async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: true,
withData: true,
type: FileType.custom,
allowedExtensions: allowedExtensions,
);
if (result != null) {
for (final pf in result.files) {
final ext = pf.extension?.toLowerCase() ?? '';
final sizeInMB = (pf.size / (1024 * 1024));
if (!allowedExtensions.contains(ext)) {
return "Unsupported file format: ${pf.name}";
}
if (sizeInMB > maxFileSizeInMB) {
return "File too large (${pf.name}). Max $maxFileSizeInMB MB allowed.";
}
// Wrap PlatformFile in our UploadedFile model
_files.add(UploadedFile(file: pf));
}
}
return null;
}
void removeFileAt(int index) {
if (index >= 0 && index < _files.length) {
_files[index].dispose();
_files.removeAt(index);
}
}
void clearAll() {
for (final f in _files) {
f.dispose();
}
_files.clear();
}
}

View File

@ -1,462 +1,462 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../hrPolicyDetails.dart';
import '../api_service.dart';
class ActivePolicies extends StatefulWidget {
final String empClientId;
final String empClientBranchId;
final String empHrId;
final String postToken;
const ActivePolicies(
{Key? key,
required this.empClientId,
required this.empClientBranchId,
required this.postToken,
required this.empHrId});
@override
State<ActivePolicies> createState() => _ActivePolicieState();
}
class _ActivePolicieState extends State<ActivePolicies> {
late ApiService apiService;
dynamic getCardArrays = [];
int selectedIndex = 1;
int stausVal = 1;
bool isLoading = false;
// List<Map<String, dynamic>> getCardArrays = [
// {
// "client_policy_id": 392,
// "client_id": 58,
// "policy_type_id": 2,
// "is_addon": 1,
// "OpenForEnrollment": 1,
// "inception_type": 2,
// "policy_no": "GMC-MA8596745566998855885",
// "insurer_id": 1,
// "type": "GMC",
// "policy_name": "Group Medical Coverage",
// "insurer_name": "Life Insurance Corporation of India (LIC)",
// "insurer_short_name": "LIC",
// "totalMembersCount": 7,
// "membersCountOfEnrolled": 0,
// "membersCountOfDraft": 7
// },
// {
// "client_policy_id": 392,
// "client_id": 58,
// "policy_type_id": 2,
// "is_addon": 1,
// "OpenForEnrollment": 1,
// "inception_type": 2,
// "policy_no": "GMC-MA8596745566998855885",
// "insurer_id": 1,
// "type": "GMC",
// "policy_name": "Group Medical Coverage",
// "insurer_name": "Life Insurance Corporation of India (LIC)",
// "insurer_short_name": "LIC",
// "totalMembersCount": 7,
// "membersCountOfEnrolled": 0,
// "membersCountOfDraft": 7
// },
//
// ];
@override
void initState() {
super.initState();
apiService = ApiService(context);
_loadData();
print("_PreEnrollmentState 1");
}
Future<void> _loadData() async {
await getCashDepositDetails(widget.empClientBranchId, widget.empClientId,
widget.empHrId, widget.postToken);
}
Future<void> getCashDepositDetails(
clintBranchId, clintID, hr_id, token) async {
print("_PreEnrollmentState 2");
print('IN');
print("clintBranchId -$clintBranchId");
print("clintID -$clintID");
print("hr_id -$hr_id");
print("token -$token");
isLoading = true;
// setState(() {
// _isLoading = true;
// });
try {
if (clintBranchId == null || clintID == null) {
return;
}
final response = await apiService.getActiveCashDepositDetailsToApi(
clintID!, clintBranchId!, hr_id, token, stausVal);
// final response = await apiService.getCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token);
print('IN1');
if (response['status'] == 'success') {
isLoading = false;
setState(() {
print('response');
print(response['data']);
print("_PreEnrollmentState 3");
setState(() {
getCardArrays = List<Map<String, dynamic>>.from(response['data']);
});
print('getCardArrays');
print(getCardArrays);
});
print('IN2');
print("getCardArrays9 - $getCardArrays");
} else {
isLoading = false;
print('API request failed with status');
setState(() {
getCardArrays = [];
});
}
} catch (e) {
print('Exception occurred: $e');
}
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final crossAxisCount = 4;
final spacing = 20.0; // crossAxisSpacing
final totalSpacing = (crossAxisCount - 1) * spacing;
final itemWidth = (screenWidth - totalSpacing) / crossAxisCount;
// Example: target card height
final itemHeight = screenHeight * 0.2;
// Dynamic aspect ratio:
final aspectRatio = screenWidth / screenHeight;
print("_PreEnrollmentState 4");
// TODO: implement build
return Container(
// height: MediaQuery.of(context).size.height * 0.2,
// height: 400,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.white,
// color: Colors.yellow.shade100,
borderRadius: BorderRadius.circular(16),
),
//
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
// padding: const EdgeInsets.only(left: 16.0, right: 16.0),
decoration: BoxDecoration(
color: Colors.grey.shade50,
boxShadow: const [
BoxShadow(
color: Colors.black54, // Grey shadow
spreadRadius: 0.2,
blurRadius: 6,
offset: Offset(0, 1), // Horizontal, Vertical
),
],
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
buildTab("Active", 0),
const SizedBox(width: 10),
buildTab("Expired", 1),
],
),
),
],
),
const SizedBox(
height: 15,
),
isLoading
? Expanded(
// color: Color(0x98FFFCE5), // 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
),
)
: Flexible(
child: Container(
// color: Colors.redAccent.shade100,
// height: MediaQuery.of(context).size.height * 0.4,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
child: Container(
// color: Colors.redAccent.shade100,
// color: Colors.white,
// color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 16.0),
// height: MediaQuery.of(context).size.height * 0.4,
// height: 400,
child: getCardArrays.isEmpty
? Center(
child: Text(
'No Policy Mapping Found',
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade600,
),
),
)
: GridView.builder(
itemCount: getCardArrays.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
// childAspectRatio: 2,
childAspectRatio: aspectRatio,
// childAspectRatio: 2.1,
),
itemBuilder: (context, index) {
return buildPolicyCard(getCardArrays[index]);
},
),
),
// ],
),
),
],
),
);
}
Widget buildPolicyCard(Map<String, dynamic> policy) {
print("buildPolicyCard - $policy");
final mediaQuery = MediaQuery.of(context);
final devicePixelRatio = mediaQuery.devicePixelRatio;
final logicalWidth = 230 / devicePixelRatio;
final logicalHeight = 115 / devicePixelRatio;
print("logicalWidth - $logicalWidth");
print("logicalHeight - $logicalHeight");
return InkWell(
onTap: () {
setState(() {
print("policytab - $policy");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => hrPolicyDetails(
ClientId: policy['client_id'].toString(), // <-- from map
policyTypeId:
policy['policy_type_id'].toString(), // <-- from map
ClientPoliyId: policy['client_policy_id'].toString(),
clientBranchId: widget.empClientBranchId,
Token: widget.postToken,
TokenType: 'post',
cardType: policy['type'].toString(),
cardPolicyNo: policy['policy_no'].toString(),
cardInsurer_name: policy['insurer_short_name'].toString(),
cardPolicy_name: policy['policy_name'].toString(),
cardPolicy_ExpDate: policy['policy_expiry_date'].toString(),
),
),
);
});
},
// height: MediaQuery.of(context).size.height * 0.1,
// width: MediaQuery.of(context).size.height * 0.1,
// width: logicalWidth,
// height: logicalHeight,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
// height: MediaQuery.of(context).size.height * 1,
// width: MediaQuery.of(context).size.height * 0.1,
// margin: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
// color: Colors.yellow.shade50,
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
spreadRadius: 1,
offset: Offset(0, 0), // Equal shadow in all directions
),
],
),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: MediaQuery.of(context).size.height * 0.07,
// color: Colors.green.shade100,
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${policy['type']} - ${policy['policy_no']} " ?? '',
style: const TextStyle(
fontFamily: "Inter",
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
policy['insurer_name'] != ''
? Text(
// "${policy['insurer_short_name']} - ${policy['policy_name']} " ??
// '',
policy['insurer_name'] ?? "",
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
)
: SizedBox.shrink(),
],
),
],
),
),
// Spacer(),
Container(
// color: Colors.pink.shade50,
height: MediaQuery.of(context).size.height * 0.09,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCountBox(
policy['membersCountOfActive'].toString(),
"Active",
Color(0xFF7BD9B6),
),
// Spacer(),
_buildCountBox(
policy['membersCountOfInactive'].toString(),
"Inactive",
Color(0xFFFFA6A6),
),
// Spacer(),
// _buildCountBox(
// policy['totalMembersCount'].toString(), "Total"),
// Spacer(),
],
),
),
],
),
),
),
);
}
Widget _buildCountBox(String count, String label, Color boxColor) {
return Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
// width: 70,
// height: 40,
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.height * 0.1,
alignment: Alignment.center,
decoration: BoxDecoration(
color: boxColor,
// color: const Color(0xFFDFF1F3),
borderRadius: BorderRadius.circular(8),
),
child: Text(
count,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
),
// Spacer(),
const SizedBox(height: 8),
Text(label,
style: const TextStyle(fontSize: 10, color: Color(0xFF848484))),
],
);
}
Widget buildTab(String title, int index) {
final isSelected = selectedIndex == index + 1;
return InkWell(
onTap: () {
setState(() {
selectedIndex = index + 1;
if (selectedIndex == 2) {
stausVal = 0;
} else {
stausVal = 1;
}
_loadData();
});
},
child: Container(
padding: const EdgeInsets.only(
top: 6.5, bottom: 6.0, left: 20.0, right: 20.0),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF009195) : Colors.transparent,
boxShadow: isSelected
? [
BoxShadow(
color: isSelected
? const Color(0xFF009195)
: Colors.transparent, // Grey shadow
spreadRadius: 0.2,
blurRadius: 1,
offset: const Offset(0, 1), // Horizontal, Vertical
),
]
: [],
borderRadius: BorderRadius.circular(16),
),
child: Text(title,
style: GoogleFonts.poppins(
fontSize: 11,
color: isSelected ? Colors.white : Colors.black,
fontWeight: FontWeight.w500,
))),
);
}
}
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
// import 'package:google_fonts/google_fonts.dart';
// import 'package:http/http.dart' as http;
//
// import '../../presentation/hrPolicyDetails.dart';
// import '../api_service.dart';
//
// class ActivePolicies extends StatefulWidget {
// final String empClientId;
// final String empClientBranchId;
// final String empHrId;
// final String postToken;
//
// const ActivePolicies(
// {Key? key,
// required this.empClientId,
// required this.empClientBranchId,
// required this.postToken,
// required this.empHrId});
//
// @override
// State<ActivePolicies> createState() => _ActivePolicieState();
// }
//
// class _ActivePolicieState extends State<ActivePolicies> {
// late ApiService apiService;
// dynamic getCardArrays = [];
// int selectedIndex = 1;
// int stausVal = 1;
// bool isLoading = false;
//
// // List<Map<String, dynamic>> getCardArrays = [
// // {
// // "client_policy_id": 392,
// // "client_id": 58,
// // "policy_type_id": 2,
// // "is_addon": 1,
// // "OpenForEnrollment": 1,
// // "inception_type": 2,
// // "policy_no": "GMC-MA8596745566998855885",
// // "insurer_id": 1,
// // "type": "GMC",
// // "policy_name": "Group Medical Coverage",
// // "insurer_name": "Life Insurance Corporation of India (LIC)",
// // "insurer_short_name": "LIC",
// // "totalMembersCount": 7,
// // "membersCountOfEnrolled": 0,
// // "membersCountOfDraft": 7
// // },
// // {
// // "client_policy_id": 392,
// // "client_id": 58,
// // "policy_type_id": 2,
// // "is_addon": 1,
// // "OpenForEnrollment": 1,
// // "inception_type": 2,
// // "policy_no": "GMC-MA8596745566998855885",
// // "insurer_id": 1,
// // "type": "GMC",
// // "policy_name": "Group Medical Coverage",
// // "insurer_name": "Life Insurance Corporation of India (LIC)",
// // "insurer_short_name": "LIC",
// // "totalMembersCount": 7,
// // "membersCountOfEnrolled": 0,
// // "membersCountOfDraft": 7
// // },
// //
// // ];
//
// @override
// void initState() {
// super.initState();
// apiService = ApiService(context);
// _loadData();
//
// print("_PreEnrollmentState 1");
// }
//
// Future<void> _loadData() async {
// await getCashDepositDetails(widget.empClientBranchId, widget.empClientId,
// widget.empHrId, widget.postToken);
// }
//
// Future<void> getCashDepositDetails(
// clintBranchId, clintID, hr_id, token) async {
// print("_PreEnrollmentState 2");
// print('IN');
// print("clintBranchId -$clintBranchId");
// print("clintID -$clintID");
// print("hr_id -$hr_id");
// print("token -$token");
//
// isLoading = true;
// // setState(() {
// // _isLoading = true;
// // });
// try {
// if (clintBranchId == null || clintID == null) {
// return;
// }
//
// final response = await apiService.getActiveCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token, stausVal);
//
// // final response = await apiService.getCashDepositDetailsToApi(
// // clintID!, clintBranchId!, hr_id, token);
// print('IN1');
// if (response['status'] == 'success') {
// isLoading = false;
// setState(() {
// print('response');
// print(response['data']);
//
// print("_PreEnrollmentState 3");
// setState(() {
// getCardArrays = List<Map<String, dynamic>>.from(response['data']);
// });
//
// print('getCardArrays');
// print(getCardArrays);
// });
//
// print('IN2');
// print("getCardArrays9 - $getCardArrays");
// } else {
// isLoading = false;
// print('API request failed with status');
// setState(() {
// getCardArrays = [];
// });
// }
// } catch (e) {
// print('Exception occurred: $e');
// }
// }
//
// @override
// Widget build(BuildContext context) {
// final screenWidth = MediaQuery.of(context).size.width;
// final screenHeight = MediaQuery.of(context).size.height;
//
// final crossAxisCount = 4;
// final spacing = 20.0; // crossAxisSpacing
// final totalSpacing = (crossAxisCount - 1) * spacing;
// final itemWidth = (screenWidth - totalSpacing) / crossAxisCount;
//
// // Example: target card height
// final itemHeight = screenHeight * 0.2;
//
// // Dynamic aspect ratio:
// final aspectRatio = screenWidth / screenHeight;
//
// print("_PreEnrollmentState 4");
// // TODO: implement build
// return Container(
// // height: MediaQuery.of(context).size.height * 0.2,
//
// // height: 400,
// padding: const EdgeInsets.all(16.0),
// decoration: BoxDecoration(
// color: Colors.white,
// // color: Colors.yellow.shade100,
// borderRadius: BorderRadius.circular(16),
// ),
// //
//
// child: Column(
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// Container(
// // padding: const EdgeInsets.only(left: 16.0, right: 16.0),
// decoration: BoxDecoration(
// color: Colors.grey.shade50,
// boxShadow: const [
// BoxShadow(
// color: Colors.black54, // Grey shadow
// spreadRadius: 0.2,
// blurRadius: 6,
// offset: Offset(0, 1), // Horizontal, Vertical
// ),
// ],
// borderRadius: BorderRadius.circular(16),
// ),
//
// child: Row(
// children: [
// buildTab("Active", 0),
// const SizedBox(width: 10),
// buildTab("Expired", 1),
// ],
// ),
// ),
// ],
// ),
// const SizedBox(
// height: 15,
// ),
// isLoading
// ? Expanded(
// // color: Color(0x98FFFCE5), // 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
// ),
// )
// : Flexible(
// child: Container(
// // color: Colors.redAccent.shade100,
// // height: MediaQuery.of(context).size.height * 0.4,
// // mainAxisAlignment: MainAxisAlignment.center,
// // children: [
// child: Container(
// // color: Colors.redAccent.shade100,
// // color: Colors.white,
// // color: Colors.white,
// padding: EdgeInsets.symmetric(horizontal: 16.0),
// // height: MediaQuery.of(context).size.height * 0.4,
// // height: 400,
//
// child: getCardArrays.isEmpty
// ? Center(
// child: Text(
// 'No Policy Mapping Found',
// style: TextStyle(
// fontSize: 16,
// color: Colors.grey.shade600,
// ),
// ),
// )
// : GridView.builder(
// itemCount: getCardArrays.length,
// gridDelegate:
// SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 4,
// crossAxisSpacing: 20,
// mainAxisSpacing: 20,
// // childAspectRatio: 2,
// childAspectRatio: aspectRatio,
// // childAspectRatio: 2.1,
// ),
// itemBuilder: (context, index) {
// return buildPolicyCard(getCardArrays[index]);
// },
// ),
// ),
// // ],
// ),
// ),
// ],
// ),
// );
// }
//
// Widget buildPolicyCard(Map<String, dynamic> policy) {
// print("buildPolicyCard - $policy");
// final mediaQuery = MediaQuery.of(context);
// final devicePixelRatio = mediaQuery.devicePixelRatio;
// final logicalWidth = 230 / devicePixelRatio;
// final logicalHeight = 115 / devicePixelRatio;
//
// print("logicalWidth - $logicalWidth");
// print("logicalHeight - $logicalHeight");
// return InkWell(
// onTap: () {
// setState(() {
// print("policytab - $policy");
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => hrPolicyDetails(
// ClientId: widget.empClientId, // <-- from map
// policyTypeId:
// policy['policy_type_id'].toString(), // <-- from map
// ClientPoliyId: policy['client_policy_id'].toString(),
// clientBranchId: widget.empClientBranchId,
// Token: widget.postToken,
// TokenType: 'post',
// cardType: policy['type'].toString(),
// cardPolicyNo: policy['policy_no'].toString(),
// cardInsurer_name: policy['insurer_short_name'].toString(),
// cardPolicy_name: policy['policy_name'].toString(),
// cardPolicy_ExpDate: policy['policy_expiry_date'].toString(),
// ),
// ),
// );
// });
// },
//
// // height: MediaQuery.of(context).size.height * 0.1,
// // width: MediaQuery.of(context).size.height * 0.1,
// // width: logicalWidth,
// // height: logicalHeight,
// child: Container(
// margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
// // height: MediaQuery.of(context).size.height * 1,
// // width: MediaQuery.of(context).size.height * 0.1,
// // margin: EdgeInsets.only(bottom: 10.0),
// decoration: BoxDecoration(
// // color: Colors.yellow.shade50,
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// boxShadow: const [
// BoxShadow(
// color: Colors.black12,
// blurRadius: 6,
// spreadRadius: 1,
// offset: Offset(0, 0), // Equal shadow in all directions
// ),
// ],
// ),
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Container(
// height: MediaQuery.of(context).size.height * 0.07,
// // color: Colors.green.shade100,
// child: Row(
// children: [
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "${policy['type']} - ${policy['policy_no']} " ?? '',
// style: const TextStyle(
// fontFamily: "Inter",
// fontWeight: FontWeight.w600,
// fontSize: 12,
// ),
// ),
// policy['insurer_name'] != ''
// ? Text(
// // "${policy['insurer_short_name']} - ${policy['policy_name']} " ??
// // '',
//
// policy['insurer_name'] ?? "",
// style: const TextStyle(
// fontSize: 11,
// color: Colors.grey,
// ),
// )
// : SizedBox.shrink(),
// ],
// ),
// ],
// ),
// ),
// // Spacer(),
// Container(
// // color: Colors.pink.shade50,
// height: MediaQuery.of(context).size.height * 0.09,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// _buildCountBox(
// policy['membersCountOfActive'].toString(),
// "Active",
// Color(0xFF7BD9B6),
// ),
//
// // Spacer(),
// _buildCountBox(
// policy['membersCountOfInactive'].toString(),
// "Inactive",
// Color(0xFFFFA6A6),
// ),
// // Spacer(),
// // _buildCountBox(
// // policy['totalMembersCount'].toString(), "Total"),
// // Spacer(),
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
//
// Widget _buildCountBox(String count, String label, Color boxColor) {
// return Column(
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Container(
// // width: 70,
// // height: 40,
// height: MediaQuery.of(context).size.height * 0.05,
// width: MediaQuery.of(context).size.height * 0.1,
// alignment: Alignment.center,
// decoration: BoxDecoration(
// color: boxColor,
// // color: const Color(0xFFDFF1F3),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Text(
// count,
// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
// ),
// ),
// // Spacer(),
// const SizedBox(height: 8),
// Text(label,
// style: const TextStyle(fontSize: 10, color: Color(0xFF848484))),
// ],
// );
// }
//
// Widget buildTab(String title, int index) {
// final isSelected = selectedIndex == index + 1;
// return InkWell(
// onTap: () {
// setState(() {
// selectedIndex = index + 1;
// if (selectedIndex == 2) {
// stausVal = 0;
// } else {
// stausVal = 1;
// }
// _loadData();
// });
// },
// child: Container(
// padding: const EdgeInsets.only(
// top: 6.5, bottom: 6.0, left: 20.0, right: 20.0),
// decoration: BoxDecoration(
// color: isSelected ? const Color(0xFF009195) : Colors.transparent,
// boxShadow: isSelected
// ? [
// BoxShadow(
// color: isSelected
// ? const Color(0xFF009195)
// : Colors.transparent, // Grey shadow
// spreadRadius: 0.2,
// blurRadius: 1,
// offset: const Offset(0, 1), // Horizontal, Vertical
// ),
// ]
// : [],
// borderRadius: BorderRadius.circular(16),
// ),
// child: Text(title,
// style: GoogleFonts.poppins(
// fontSize: 11,
// color: isSelected ? Colors.white : Colors.black,
// fontWeight: FontWeight.w500,
// ))),
// );
// }
// }

View File

@ -7,13 +7,14 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import '../../cdTransactionDetails.dart';
import '../../presentation/cdTransactionDetails.dart';
import '../api_service.dart';
import 'package:collection/collection.dart';
import '../token_storage_service.dart';
class CdPolicies extends StatefulWidget {
final String empClientId;
final String empClientBranchId;
@ -31,6 +32,7 @@ class CdPolicies extends StatefulWidget {
}
class _CdPolicieState extends State<CdPolicies> {
final tokenService = TokenStorageService();
Uint8List? fileBytes;
List<Map<String, dynamic>> getCDPolicies = [];
bool isLoading = false;
@ -188,9 +190,8 @@ class _CdPolicieState extends State<CdPolicies> {
Future<void> handleExportAction() async {
print('handleExportAction');
final SharedPreferences prefs = await SharedPreferences.getInstance();
final postId = prefs.getString('empHrId');
final preId = prefs.getString('enrollmentEmpPrimaryId');
final postId = await tokenService.readValue('empHrId');
final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
var activity = "export_cddata";
print('postId - $postId');
@ -502,8 +503,7 @@ class _CdPolicieState extends State<CdPolicies> {
cdMasterAccountNo: item['cd_master_account_no'],
insurerId: item['insurer_id'],
cd_ac_pk: item['cd_ac_pk'],
empClientId: widget.empClientId,
postToken: widget.postToken),
empClientId: widget.empClientId),
),
);
},

View File

@ -1,374 +1,374 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../hrPolicyDetails.dart';
import '../api_service.dart';
class PreEnrollment extends StatefulWidget {
final String enrollmentClientId;
final String enrollmentClientBranchId;
final String enrollmentHrId;
final String enrollToken;
const PreEnrollment(
{Key? key,
required this.enrollmentClientId,
required this.enrollmentClientBranchId,
required this.enrollmentHrId,
required this.enrollToken});
@override
State<PreEnrollment> createState() => _PreEnrollmentState();
}
class _PreEnrollmentState extends State<PreEnrollment> {
late ApiService apiService;
dynamic getCardArrays = [];
int selectedIndex = 0;
bool isLoading = false;
// List<Map<String, dynamic>> getCardArrays = [
// {
// "client_policy_id": "415",
// "client_id": "187",
// "policy_type_id": "2",
// "is_addon": "1",
// "OpenForEnrollment": "0",
// "inception_type": "1",
// "policy_no": "GMC-2002/2022/2005",
// "insurer_id": null,
// "policy_expiry_date": "06-07-2026",
// "type": "GMC",
// "policy_name": "Group Medical Coverage",
// "insurer_name": null,
// "insurer_short_name": null,
// "totalMembersCount": 17,
// "membersCountOfEnrolled": 0,
// "membersCountOfDraft": 17
// },
// {
// "client_policy_id": 396,
// "client_id": 58,
// "policy_type_id": 4,
// "is_addon": 2,
// "OpenForEnrollment": 1,
// "inception_type": 2,
// "policy_no": "GMC-MA8596745566998855885",
// "insurer_id": 1,
// "type": "GMC - Topup",
// "policy_name": "Group Medical Coverage Topup",
// "insurer_name": "Life Insurance Corporation of India (LIC)",
// "insurer_short_name": "LIC",
// "totalMembersCount": 7,
// "membersCountOfEnrolled": 0,
// "membersCountOfDraft": 7
// },
//
// ];
@override
void initState() {
super.initState();
apiService = ApiService(context);
_loadData();
print("_PreEnrollmentState 1");
}
Future<void> _loadData() async {
await getCashDepositDetails(widget.enrollmentClientBranchId,
widget.enrollmentClientId, widget.enrollmentHrId, widget.enrollToken);
}
Future<void> getCashDepositDetails(
clintBranchId, clintID, hr_id, token) async {
print("_PreEnrollmentState 2");
print('IN');
print("clintBranchId -$clintBranchId");
print("clintID -$clintID");
print("hr_id -$hr_id");
print("token -$token");
// isLoading = true;
setState(() {
isLoading = true;
});
try {
if (clintBranchId == null || clintID == null) {
return;
}
final response = await apiService.getCashDepositDetailsToApi(
clintID!, clintBranchId!, hr_id, token);
// final response = await apiService.getCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token);
print('IN1');
if (response['status'] == 'success') {
setState(() {
isLoading = false;
print('response');
print(response['data']);
print("_PreEnrollmentState 3");
// getCardArrays = [];
getCardArrays = List<Map<String, dynamic>>.from(response['data']);
print('getCardArrays');
print(getCardArrays);
});
print('IN2');
print("getCardArrays9 - $getCardArrays");
} else {
setState(() {
isLoading = false;
});
print('API request failed with status');
}
} catch (e) {
print('Exception occurred: $e');
}
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final crossAxisCount = 4;
final spacing = 20.0; // crossAxisSpacing
final totalSpacing = (crossAxisCount - 1) * spacing;
final itemWidth = (screenWidth - totalSpacing) / crossAxisCount;
// Example: target card height
final itemHeight = screenHeight * 0.2;
// Dynamic aspect ratio:
final aspectRatio = screenWidth / screenHeight;
print("_PreEnrollmentState 4");
// TODO: implement build
return Container(
// height: MediaQuery.of(context).size.height * 0.2,
// height: 400,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.white,
// color: Colors.yellow.shade100,
borderRadius: BorderRadius.circular(16),
),
//
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
isLoading
? Container(
// color: Color(0x98FFFCE5), // 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
),
)
: Flexible(
child: Container(
// color: Colors.redAccent.shade100,
// color: Colors.white,
// color: Colors.white,
// padding: EdgeInsets.symmetric(horizontal: 16.0),
// height: MediaQuery.of(context).size.height * 0.45,
// height: 400,
child: Container(
child: getCardArrays.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/searchData.jpg', // Replace 'default_image.png' with your default image asset path
width: 200,
height: 200,
fit: BoxFit.cover,
),
Text(
'No Policy Mapping Found',
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade600,
),
),
],
),
)
: GridView.builder(
itemCount: getCardArrays.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
childAspectRatio: 2.4,
// childAspectRatio: aspectRatio,
// childAspectRatio: 2.1,
),
itemBuilder: (context, index) {
return buildPolicyCard(getCardArrays[index]);
},
),
),
),
),
],
),
);
}
Widget buildPolicyCard(Map<String, dynamic> policy) {
final mediaQuery = MediaQuery.of(context);
final devicePixelRatio = mediaQuery.devicePixelRatio;
final logicalWidth = 230 / devicePixelRatio;
final logicalHeight = 189 / devicePixelRatio;
print("logicalWidth - $logicalWidth");
print("logicalHeight - $logicalHeight");
return InkWell(
onTap: () {
setState(() {
print("policytab - $policy");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => hrPolicyDetails(
ClientId: policy['client_id'].toString(), // <-- from map
policyTypeId:
policy['policy_type_id'].toString(), // <-- from map
ClientPoliyId: policy['client_policy_id'].toString(),
clientBranchId: widget.enrollmentClientBranchId,
Token: widget.enrollToken,
TokenType: "pre",
cardType: policy['type'].toString(),
cardPolicyNo: policy['policy_no'].toString(),
cardInsurer_name: policy['insurer_short_name'].toString(),
cardPolicy_name: policy['policy_name'].toString(),
cardPolicy_ExpDate: policy['policy_expiry_date'].toString(),
),
),
);
});
},
// height: MediaQuery.of(context).size.height * 0.1,
// width: MediaQuery.of(context).size.height * 0.1,
// width: logicalWidth,
// height: logicalHeight,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
// height: MediaQuery.of(context).size.height * 1,
// width: MediaQuery.of(context).size.height * 0.1,
// margin: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
// color: Colors.yellow.shade50,
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
spreadRadius: 1,
offset: Offset(0, 0), // Equal shadow in all directions
),
],
),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: MediaQuery.of(context).size.height * 0.03,
// color: Colors.green.shade100,
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${policy['type']} - ${policy['policy_no']} " ?? '',
style: const TextStyle(
fontFamily: "Inter",
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
],
),
],
),
),
// Spacer(),
SizedBox(
height: 5,
),
Container(
// color: Colors.pink.shade50,
height: MediaQuery.of(context).size.height * 0.09,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildCountBox(
policy['membersCountOfDraft'].toString(), "Draft"),
Spacer(),
_buildCountBox(policy['membersCountOfEnrolled'].toString(),
"Enrolled"),
Spacer(),
_buildCountBox(
policy['totalMembersCount'].toString(), "Total"),
// Spacer(),
],
),
),
],
),
),
),
);
}
Widget _buildCountBox(String count, String label) {
return Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
// width: 70,
// height: 40,
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.height * 0.1,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFFDFF1F3),
borderRadius: BorderRadius.circular(8),
),
child: Text(
count,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
),
// Spacer(),
const SizedBox(height: 8),
Text(label,
style: const TextStyle(fontSize: 10, color: Color(0xFF848484))),
],
);
}
}
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
// import 'package:google_fonts/google_fonts.dart';
// import 'package:http/http.dart' as http;
//
// import '../../presentation/hrPolicyDetails.dart';
// import '../api_service.dart';
//
// class PreEnrollment extends StatefulWidget {
// final String enrollmentClientId;
// final String enrollmentClientBranchId;
// final String enrollmentHrId;
// final String enrollToken;
//
// const PreEnrollment(
// {Key? key,
// required this.enrollmentClientId,
// required this.enrollmentClientBranchId,
// required this.enrollmentHrId,
// required this.enrollToken});
//
// @override
// State<PreEnrollment> createState() => _PreEnrollmentState();
// }
//
// class _PreEnrollmentState extends State<PreEnrollment> {
// late ApiService apiService;
// dynamic getCardArrays = [];
// int selectedIndex = 0;
// bool isLoading = false;
// // List<Map<String, dynamic>> getCardArrays = [
// // {
// // "client_policy_id": "415",
// // "client_id": "187",
// // "policy_type_id": "2",
// // "is_addon": "1",
// // "OpenForEnrollment": "0",
// // "inception_type": "1",
// // "policy_no": "GMC-2002/2022/2005",
// // "insurer_id": null,
// // "policy_expiry_date": "06-07-2026",
// // "type": "GMC",
// // "policy_name": "Group Medical Coverage",
// // "insurer_name": null,
// // "insurer_short_name": null,
// // "totalMembersCount": 17,
// // "membersCountOfEnrolled": 0,
// // "membersCountOfDraft": 17
// // },
// // {
// // "client_policy_id": 396,
// // "client_id": 58,
// // "policy_type_id": 4,
// // "is_addon": 2,
// // "OpenForEnrollment": 1,
// // "inception_type": 2,
// // "policy_no": "GMC-MA8596745566998855885",
// // "insurer_id": 1,
// // "type": "GMC - Topup",
// // "policy_name": "Group Medical Coverage Topup",
// // "insurer_name": "Life Insurance Corporation of India (LIC)",
// // "insurer_short_name": "LIC",
// // "totalMembersCount": 7,
// // "membersCountOfEnrolled": 0,
// // "membersCountOfDraft": 7
// // },
// //
// // ];
//
// @override
// void initState() {
// super.initState();
// apiService = ApiService(context);
// _loadData();
//
// print("_PreEnrollmentState 1");
// }
//
// Future<void> _loadData() async {
// await getCashDepositDetails(widget.enrollmentClientBranchId,
// widget.enrollmentClientId, widget.enrollmentHrId, widget.enrollToken);
// }
//
// Future<void> getCashDepositDetails(
// clintBranchId, clintID, hr_id, token) async {
// print("_PreEnrollmentState 2");
// print('IN');
// print("clintBranchId -$clintBranchId");
// print("clintID -$clintID");
// print("hr_id -$hr_id");
// print("token -$token");
//
// // isLoading = true;
// setState(() {
// isLoading = true;
// });
// try {
// if (clintBranchId == null || clintID == null) {
// return;
// }
// final response = await apiService.getCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token);
//
// // final response = await apiService.getCashDepositDetailsToApi(
// // clintID!, clintBranchId!, hr_id, token);
// print('IN1');
// if (response['status'] == 'success') {
//
// setState(() {
// isLoading = false;
// print('response');
// print(response['data']);
//
// print("_PreEnrollmentState 3");
// // getCardArrays = [];
// getCardArrays = List<Map<String, dynamic>>.from(response['data']);
// print('getCardArrays');
// print(getCardArrays);
// });
//
// print('IN2');
// print("getCardArrays9 - $getCardArrays");
// } else {
// setState(() {
// isLoading = false;
// });
//
// print('API request failed with status');
// }
// } catch (e) {
// print('Exception occurred: $e');
// }
// }
//
// @override
// Widget build(BuildContext context) {
// final screenWidth = MediaQuery.of(context).size.width;
// final screenHeight = MediaQuery.of(context).size.height;
//
// final crossAxisCount = 4;
// final spacing = 20.0; // crossAxisSpacing
// final totalSpacing = (crossAxisCount - 1) * spacing;
// final itemWidth = (screenWidth - totalSpacing) / crossAxisCount;
//
// // Example: target card height
// final itemHeight = screenHeight * 0.2;
//
// // Dynamic aspect ratio:
// final aspectRatio = screenWidth / screenHeight;
//
// print("_PreEnrollmentState 4");
// // TODO: implement build
// return Container(
// // height: MediaQuery.of(context).size.height * 0.2,
//
// // height: 400,
// padding: const EdgeInsets.all(16.0),
// decoration: BoxDecoration(
// color: Colors.white,
// // color: Colors.yellow.shade100,
// borderRadius: BorderRadius.circular(16),
// ),
// //
// child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// isLoading
// ? Container(
// // color: Color(0x98FFFCE5), // 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
// ),
// )
// : Flexible(
// child: Container(
// // color: Colors.redAccent.shade100,
// // color: Colors.white,
// // color: Colors.white,
// // padding: EdgeInsets.symmetric(horizontal: 16.0),
// // height: MediaQuery.of(context).size.height * 0.45,
// // height: 400,
//
// child: Container(
// child: getCardArrays.isEmpty
// ? Center(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Image.asset(
// 'assets/searchData.jpg', // Replace 'default_image.png' with your default image asset path
// width: 200,
// height: 200,
// fit: BoxFit.cover,
// ),
// Text(
// 'No Policy Mapping Found',
// style: TextStyle(
// fontSize: 16,
// color: Colors.grey.shade600,
// ),
// ),
// ],
// ),
// )
// : GridView.builder(
// itemCount: getCardArrays.length,
// gridDelegate:
// const SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 4,
// crossAxisSpacing: 20,
// mainAxisSpacing: 20,
// childAspectRatio: 2.4,
// // childAspectRatio: aspectRatio,
// // childAspectRatio: 2.1,
// ),
// itemBuilder: (context, index) {
// return buildPolicyCard(getCardArrays[index]);
// },
// ),
// ),
// ),
// ),
// ],
// ),
// );
// }
//
// Widget buildPolicyCard(Map<String, dynamic> policy) {
// final mediaQuery = MediaQuery.of(context);
// final devicePixelRatio = mediaQuery.devicePixelRatio;
// final logicalWidth = 230 / devicePixelRatio;
// final logicalHeight = 189 / devicePixelRatio;
//
// print("logicalWidth - $logicalWidth");
// print("logicalHeight - $logicalHeight");
// return InkWell(
// onTap: () {
// setState(() {
// print("policytab - $policy");
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => hrPolicyDetails(
// ClientId: widget.enrollmentClientId, // <-- from map
// policyTypeId:
// policy['policy_type_id'].toString(), // <-- from map
// ClientPoliyId: policy['client_policy_id'].toString(),
// clientBranchId: widget.enrollmentClientBranchId,
// Token: widget.enrollToken,
// TokenType: "pre",
// cardType: policy['type'].toString(),
// cardPolicyNo: policy['policy_no'].toString(),
// cardInsurer_name: policy['insurer_short_name'].toString(),
// cardPolicy_name: policy['policy_name'].toString(),
// cardPolicy_ExpDate: policy['policy_expiry_date'].toString(),
// ),
// ),
// );
// });
// },
//
// // height: MediaQuery.of(context).size.height * 0.1,
// // width: MediaQuery.of(context).size.height * 0.1,
// // width: logicalWidth,
// // height: logicalHeight,
// child: Container(
// margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
// // height: MediaQuery.of(context).size.height * 1,
// // width: MediaQuery.of(context).size.height * 0.1,
// // margin: EdgeInsets.only(bottom: 10.0),
// decoration: BoxDecoration(
// // color: Colors.yellow.shade50,
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// boxShadow: const [
// BoxShadow(
// color: Colors.black12,
// blurRadius: 6,
// spreadRadius: 1,
// offset: Offset(0, 0), // Equal shadow in all directions
// ),
// ],
// ),
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Container(
// height: MediaQuery.of(context).size.height * 0.03,
// // color: Colors.green.shade100,
// child: Row(
// children: [
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "${policy['type']} - ${policy['policy_no']} " ?? '',
// style: const TextStyle(
// fontFamily: "Inter",
// fontWeight: FontWeight.w600,
// fontSize: 12,
// ),
// ),
// ],
// ),
// ],
// ),
// ),
// // Spacer(),
// SizedBox(
// height: 5,
// ),
// Container(
// // color: Colors.pink.shade50,
// height: MediaQuery.of(context).size.height * 0.09,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// _buildCountBox(
// policy['membersCountOfDraft'].toString(), "Draft"),
// Spacer(),
// _buildCountBox(policy['membersCountOfEnrolled'].toString(),
// "Enrolled"),
// Spacer(),
// _buildCountBox(
// policy['totalMembersCount'].toString(), "Total"),
// // Spacer(),
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
//
// Widget _buildCountBox(String count, String label) {
// return Column(
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Container(
// // width: 70,
// // height: 40,
// height: MediaQuery.of(context).size.height * 0.05,
// width: MediaQuery.of(context).size.height * 0.1,
// alignment: Alignment.center,
// decoration: BoxDecoration(
// color: const Color(0xFFDFF1F3),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Text(
// count,
// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
// ),
// ),
// // Spacer(),
// const SizedBox(height: 8),
// Text(label,
// style: const TextStyle(fontSize: 10, color: Color(0xFF848484))),
// ],
// );
// }
// }

View File

@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import '../responsive.dart';
import 'file_upload_service.dart';
class MultiFileUploadWidget extends StatefulWidget {
final bool forceMobile;
const MultiFileUploadWidget({super.key, this.forceMobile = false});
@override
State<MultiFileUploadWidget> createState() => _MultiFileUploadWidgetState();
static bool hasFiles = false;
}
class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
final fileService = FileUploadService();
String? errorMessage;
void _pickFiles() async {
final error = await fileService.pickFiles(maxFileSizeInMB: 10); // 5 MB limit
if (error != null) {
if (mounted) {
setState(() {
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 {
setState(() {
errorMessage = null;
MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty;
});
}
}
void _removeFile(int index) {
fileService.removeFileAt(index);
setState(() {
MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty;
});
}
@override
Widget build(BuildContext context) {
final files = fileService.files;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.forceMobile || Responsive.isMobile(context)) ...[
OutlinedButton.icon(
onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFF00999E), size: 24),
label: const Text(
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFF00999E)),
),
),
const SizedBox(height: 6),
const Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey),
),
] else ...[
Row(
children: [
OutlinedButton.icon(
onPressed: _pickFiles,
icon: const Icon(Icons.file_upload_outlined,
color: Color(0xFF00999E), size: 24),
label: const Text(
"Upload Documents",
style: TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFF00999E)),
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
style: TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis,
),
),
],
),
// InkWell(
// onTap: _pickFiles, // SAME FUNCTION
// child: Container(
// width: double.infinity,
// padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
// decoration: BoxDecoration(
// color: const Color(0xFFF9F9F9),
// borderRadius: BorderRadius.circular(12),
// border: Border.all(
// color: const Color(0xFF00A6A6),
// width: 1,
// style: BorderStyle.solid, // dotted look via color + spacing
// ),
// ),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// /// Icon circle
// Container(
// padding: const EdgeInsets.all(10),
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(color: const Color(0xFF00A6A6)),
// ),
// child: const Icon(
// Icons.insert_drive_file,
// size: 28,
// color: Color(0xFF00A6A6),
// ),
// ),
//
// const SizedBox(height: 10),
//
// // /// File name (static text logic unchanged)
// // const Text(
// // "Sample_file.PDF",
// // style: TextStyle(
// // fontSize: 14,
// // fontWeight: FontWeight.w500,
// // ),
// // overflow: TextOverflow.ellipsis,
// // ),
// //
// // const SizedBox(height: 6),
//
// /// Helper text
// const Text(
// "(Supported formats: PDF, PNG, JPG, JPEG, HEIC | Max 10MB each)",
// style: TextStyle(
// fontSize: 11,
// color: Colors.grey,
// ),
// textAlign: TextAlign.center,
// ),
// ],
// ),
// ),
// ),
],
if (fileService.files.isEmpty && errorMessage == null) ...[
const SizedBox(height: 4),
const Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
if (errorMessage != null) ...[
const SizedBox(height: 4),
Text(
errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
],
const SizedBox(height: 8),
...fileService.files.asMap().entries.map((entry) {
final index = entry.key;
final uploaded = entry.value; // UploadedFile
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(uploaded.file.name, style: const TextStyle(fontSize: 14)),
trailing: IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => _removeFile(index),
),
),
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0, right: 8.0),
child: TextField(
controller: uploaded.controller,
decoration: const InputDecoration(
labelText: 'Enter document name',
border: OutlineInputBorder(),
isDense: true,
),
),
),
],
);
}),
],
);
}
}

View File

@ -0,0 +1,60 @@
// svg_service.dart
class SvgService {
static const String dashboard = '''
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3333 8V0H24V8H13.3333ZM0 13.3333V0H10.6667V13.3333H0ZM13.3333 24V10.6667H24V24H13.3333ZM0 24V16H10.6667V24H0ZM2.66667 10.6667H8V2.66667H2.66667V10.6667ZM16 21.3333H21.3333V13.3333H16V21.3333ZM16 5.33333H21.3333V2.66667H16V5.33333ZM2.66667 21.3333H8V18.6667H2.66667V21.3333Z" fill="white"/>
</svg>
''';
static const String policies = '''
<svg viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.3333 21C16.6 21 16.8333 20.9 17.0333 20.7C17.2333 20.5 17.3333 20.2667 17.3333 20C17.3333 19.7333 17.2333 19.5 17.0333 19.3C16.8333 19.1 16.6 19 16.3333 19C16.0667 19 15.8333 19.1 15.6333 19.3C15.4333 19.5 15.3333 19.7333 15.3333 20C15.3333 20.2667 15.4333 20.5 15.6333 20.7C15.8333 20.9 16.0667 21 16.3333 21ZM20 21C20.2667 21 20.5 20.9 20.7 20.7C20.9 20.5 21 20.2667 21 20C21 19.7333 20.9 19.5 20.7 19.3C20.5 19.1 20.2667 19 20 19C19.7333 19 19.5 19.1 19.3 19.3C19.1 19.5 19 19.7333 19 20C19 20.2667 19.1 20.5 19.3 20.7C19.5 20.9 19.7333 21 20 21ZM23.6667 21C23.9333 21 24.1667 20.9 24.3667 20.7C24.5667 20.5 24.6667 20.2667 24.6667 20C24.6667 19.7333 24.5667 19.5 24.3667 19.3C24.1667 19.1 23.9333 19 23.6667 19C23.4 19 23.1667 19.1 22.9667 19.3C22.7667 19.5 22.6667 19.7333 22.6667 20C22.6667 20.2667 22.7667 20.5 22.9667 20.7C23.1667 20.9 23.4 21 23.6667 21ZM2.66667 24C1.93333 24 1.30556 23.7389 0.783333 23.2167C0.261111 22.6944 0 22.0667 0 21.3333V2.66667C0 1.93333 0.261111 1.30556 0.783333 0.783333C1.30556 0.261111 1.93333 0 2.66667 0H21.3333C22.0667 0 22.6944 0.261111 23.2167 0.783333C23.7389 1.30556 24 1.93333 24 2.66667V11.6C23.5778 11.4 23.1444 11.2278 22.7 11.0833C22.2556 10.9389 21.8 10.8333 21.3333 10.7667V2.66667H2.66667V21.3333H10.7333C10.8 21.8222 10.9056 22.2889 11.05 22.7333C11.1944 23.1778 11.3667 23.6 11.5667 24H2.66667ZM2.66667 20V21.3333V2.66667V10.7667V10.6667V20ZM5.33333 18.6667H10.7667C10.8333 18.2 10.9389 17.7444 11.0833 17.3C11.2278 16.8556 11.3889 16.4222 11.5667 16H5.33333V18.6667ZM5.33333 13.3333H13.4667C14.1778 12.6667 14.9722 12.1111 15.85 11.6667C16.7278 11.2222 17.6667 10.9222 18.6667 10.7667V10.6667H5.33333V13.3333ZM5.33333 8H18.6667V5.33333H5.33333V8ZM20 26.6667C18.1556 26.6667 16.5833 26.0167 15.2833 24.7167C13.9833 23.4167 13.3333 21.8444 13.3333 20C13.3333 18.1556 13.9833 16.5833 15.2833 15.2833C16.5833 13.9833 18.1556 13.3333 20 13.3333C21.8444 13.3333 23.4167 13.9833 24.7167 15.2833C26.0167 16.5833 26.6667 18.1556 26.6667 20C26.6667 21.8444 26.0167 23.4167 24.7167 24.7167C23.4167 26.0167 21.8444 26.6667 20 26.6667Z" fill="white"/>
</svg>
''';
static const String cd = '''
<svg viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6667 10.8C9.67901 10.8 8.8395 10.45 8.14815 9.75C7.45679 9.05 7.11111 8.2 7.11111 7.2C7.11111 6.2 7.45679 5.35 8.14815 4.65C8.8395 3.95 9.67901 3.6 10.6667 3.6C11.6543 3.6 12.4938 3.95 13.1852 4.65C13.8765 5.35 14.2222 6.2 14.2222 7.2C14.2222 8.2 13.8765 9.05 13.1852 9.75C12.4938 10.45 11.6543 10.8 10.6667 10.8ZM2.37037 14.4C1.71852 14.4 1.16049 14.165 0.696296 13.695C0.232099 13.225 0 12.66 0 12V2.4C0 1.74 0.232099 1.175 0.696296 0.705C1.16049 0.235 1.71852 0 2.37037 0H18.963C19.6148 0 20.1728 0.235 20.637 0.705C21.1012 1.175 21.3333 1.74 21.3333 2.4V12C21.3333 12.66 21.1012 13.225 20.637 13.695C20.1728 14.165 19.6148 14.4 18.963 14.4H2.37037ZM4.74074 12H16.5926C16.5926 11.34 16.8247 10.775 17.2889 10.305C17.7531 9.835 18.3111 9.6 18.963 9.6V4.8C18.3111 4.8 17.7531 4.565 17.2889 4.095C16.8247 3.625 16.5926 3.06 16.5926 2.4H4.74074C4.74074 3.06 4.50864 3.625 4.04444 4.095C3.58025 4.565 3.02222 4.8 2.37037 4.8V9.6C3.02222 9.6 3.58025 9.835 4.04444 10.305C4.50864 10.775 4.74074 11.34 4.74074 12ZM10.6667 24L15.4074 19.2L13.7481 17.52L11.8519 19.44V15.6H9.48148V19.44L7.58518 17.52L5.92593 19.2L10.6667 24Z" fill="white"/>
</svg>
''';
static const String claims = '''
<svg width="32" height="28" viewBox="0 0 32 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.07666 9.87036L10.2632 10.4778L10.2847 10.4885L10.3062 10.4973L11.9312 11.1448L11.9556 11.1545L11.981 11.1624L13.4263 11.5667L13.4556 11.5745L13.4868 11.5793L15.5054 11.8811L15.5884 11.9553V17.5676L15.1685 18.9983L14.7476 19.9016L13.979 21.0491L13.0288 22.0549L12.3452 22.6682L10.8696 23.679L9.28467 24.468L9.02881 24.5833H8.61865L7.02881 23.8333L5.81396 23.0549L4.94678 22.3547L4.51416 21.967L3.74268 21.1985L3.06689 20.2141L2.38135 18.8303L2.04443 17.6985L1.96338 17.1184V11.9553L2.04541 11.8811L4.06494 11.5793L4.1001 11.5745L4.13428 11.5637L6.30127 10.9172L6.32764 10.9084L6.35303 10.8977L8.06885 10.1702L8.09326 10.1594L8.11768 10.1458L8.69482 9.82153L9.07666 9.87036Z" stroke="white"/>
<path d="M28.7302 7.65747H26.114L26.0388 7.61255L26.0261 7.58911V5.23267L28.7302 7.65747Z" stroke="white" stroke-width="2"/>
<path d="M12.5636 14.3857L12.6613 14.5166L12.6515 14.5615L12.2179 15.0137L11.7882 15.3984L10.9923 16.0342L10.9679 16.0537L10.9464 16.0762L10.3292 16.7051L9.54797 17.3291L9.53625 17.3379L9.52551 17.3477L9.07434 17.752L8.08118 18.6416L7.77258 18.9189H7.75208L7.67786 18.8984L7.31067 18.6084L6.9425 18.3164L6.14954 17.5273L6.14075 17.5176L6.13098 17.5088L5.12317 16.6055V16.4863L5.26672 16.3584H5.33411L7.26868 18.0918L7.46008 18.2637L7.71204 18.208L8.07239 18.127L8.20032 18.0986L8.297 18.0107L12.3693 14.3604L12.5636 14.3857Z" stroke="white"/>
<path d="M20.1507 10.2754H27.1021L27.4633 10.599L27.373 11.1654L27.1021 11.3272H20.1507L19.8799 11.0845V10.5181L20.1507 10.2754Z" fill="white"/>
<path d="M17.6227 10.0332H18.2547L18.7061 10.3569L18.7963 10.5187V11.166L18.345 11.5705L18.1644 11.6514H17.6227L17.1713 11.3278L17.0811 11.166V10.4378L17.6227 10.0332Z" fill="white"/>
<path d="M20.241 17.8008H27.0119L27.4633 18.1244V18.6099L27.1021 18.9336H20.241L19.8799 18.6099V18.1244L20.241 17.8008Z" fill="white"/>
<path d="M17.713 17.5576H18.2547L18.7061 17.8813L18.7963 18.0431V18.6904L18.4352 19.0949L17.6227 19.1759L17.1713 18.8522L17.0811 18.6904V18.0431L17.5324 17.6385L17.713 17.5576Z" fill="white"/>
<path d="M20.1507 14.0791H27.1924L27.4633 14.3218V14.8882L27.1021 15.131H20.241L19.8799 14.8882V14.3218L20.1507 14.0791Z" fill="white"/>
<path d="M17.6227 13.8359H18.345L18.7963 14.3214V14.8878L18.4352 15.2924L18.2547 15.3733H17.5324L17.0811 14.8878V14.3214L17.4422 13.9168L17.6227 13.8359Z" fill="white"/>
</svg>
''';
static const String logout = '''
<svg viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5918 1.01172C3.52258 1.07119 3.43203 1.16624 3.35938 1.30371C3.24672 1.51694 3.20333 1.78932 3.28711 2.06348C3.36047 2.30347 3.50289 2.45196 3.58301 2.52246C3.62203 2.55679 3.65579 2.58072 3.66309 2.58594C3.67621 2.59534 3.6879 2.60313 3.69434 2.60742C3.70727 2.61604 3.71907 2.62322 3.72656 2.62793C3.74252 2.63796 3.76038 2.64893 3.77734 2.65918C3.81243 2.68039 3.86002 2.70817 3.91602 2.74121C4.02884 2.80779 4.18724 2.90119 4.37891 3.0127C4.76262 3.23591 5.28462 3.53759 5.85254 3.86426C7.3511 4.72623 8.13064 5.17554 8.56836 5.43945C8.99689 5.69784 9.0357 5.74158 9.08789 5.79004C9.26336 5.95292 9.43936 6.22514 9.51855 6.45801L9.57031 6.60938L9.58301 12.4355L9.5957 18.4258L9.59863 19.4297L9.62402 19.4287L9.60547 20.1768L9.58398 21.0322C9.57424 21.4245 9.56665 21.5995 9.55371 21.71C9.54562 21.779 9.53777 21.81 9.50195 21.9111C9.31858 22.4284 8.8897 22.8238 8.36719 22.9629C8.29661 22.9816 8.13612 23.0021 7.92676 22.998C7.71774 22.994 7.55448 22.9671 7.47949 22.9443C7.5142 22.9549 7.48529 22.9485 7.3291 22.8701C7.19453 22.8026 7.0054 22.7021 6.75098 22.5625C6.24329 22.284 5.50219 21.8634 4.46191 21.2646C3.14721 20.5079 2.42809 20.0915 2.00195 19.8291C1.68862 19.6361 1.56829 19.547 1.50195 19.4893L1.44824 19.4385C1.2473 19.2396 1.09782 18.968 1.04004 18.7344C1.03934 18.7213 1.03898 18.7038 1.03809 18.6816C1.03464 18.5957 1.03153 18.4691 1.02832 18.2969C1.02193 17.9535 1.01588 17.4485 1.01172 16.7646C1.00341 15.3977 1 13.3301 1 10.4443C1 6.78857 1.00155 4.73445 1.01172 3.5625C1.01682 2.97463 1.02399 2.62104 1.0332 2.40234C1.03859 2.27441 1.04466 2.21208 1.04688 2.18945C1.18692 1.67265 1.59937 1.2299 2.16406 1.04395C2.19857 1.04092 2.38794 1.02423 3.05176 1.01562C3.20763 1.0136 3.38635 1.01319 3.5918 1.01172Z" stroke="white" stroke-width="2"/>
</svg>
''';
static String getSvg(String svgName) {
switch (svgName) {
case 'dashboard':
return dashboard;
case 'policies':
return policies;
case 'cd':
return cd;
case 'claims':
return claims;
case 'logout':
return logout;
default:
return '';
}
}
// Add more SVG strings here as needed
}

View File

@ -1,16 +1,23 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class TokenStorageService {
static final TokenStorageService _instance = TokenStorageService._internal();
static final TokenStorageService _instance =
TokenStorageService._internal();
factory TokenStorageService() => _instance;
TokenStorageService._internal();
// 🔐 Secure storage instance
static const FlutterSecureStorage _secureStorage =
FlutterSecureStorage();
// Storage keys
static const String _preEnrollmentKey = 'pre_enrollment_data';
static const String _postEnrollmentKey = 'post_enrollment_data';
static const String _selectedBranchKey = 'selected_branch';
static const String _decodedTokenKey = 'decoded_token';
static const String _branchNameKey = 'branch_name';
// In-memory cache
List<dynamic>? _preEnrollmentData;
@ -18,51 +25,64 @@ class TokenStorageService {
Map<String, dynamic>? _selectedBranch;
Map<String, dynamic>? _decodedToken;
// Initialize - Load data from storage
// 🔄 Initialize from secure storage
Future<void> initialize() async {
final prefs = await SharedPreferences.getInstance();
final preData = prefs.getString(_preEnrollmentKey);
final preData = await _secureStorage.read(key: _preEnrollmentKey);
if (preData != null) {
_preEnrollmentData = json.decode(preData);
}
final postData = prefs.getString(_postEnrollmentKey);
final postData = await _secureStorage.read(key: _postEnrollmentKey);
if (postData != null) {
_postEnrollmentData = json.decode(postData);
}
final branchData = prefs.getString(_selectedBranchKey);
final branchData =
await _secureStorage.read(key: _selectedBranchKey);
if (branchData != null) {
_selectedBranch = json.decode(branchData);
}
final tokenData = prefs.getString(_decodedTokenKey);
final tokenData =
await _secureStorage.read(key: _decodedTokenKey);
if (tokenData != null) {
_decodedToken = json.decode(tokenData);
}
}
// Save enrollment data
Future<void> saveEnrollmentData(List<dynamic> preData, List<dynamic> postData) async {
// 💾 Save enrollment data
Future<void> saveEnrollmentData(
List<dynamic> preData,
List<dynamic> postData,
) async {
_preEnrollmentData = preData;
_postEnrollmentData = postData;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_preEnrollmentKey, json.encode(preData));
await prefs.setString(_postEnrollmentKey, json.encode(postData));
await _secureStorage.write(
key: _preEnrollmentKey, value: json.encode(preData));
await _secureStorage.write(
key: _postEnrollmentKey, value: json.encode(postData));
}
// Get combined unique branches
List<Map<String, dynamic>> getCombinedBranches() {
List<Map<String, dynamic>> combined = [];
Set<String> seenTokens = {};
Set<String> seenIds = {};
// Helper: check valid token
bool hasValidToken(dynamic token) {
return token != null &&
token.toString().trim().isNotEmpty;
}
// Add pre-enrollment data
if (_preEnrollmentData != null) {
for (var item in _preEnrollmentData!) {
String token = item['token']?.toString() ?? '';
final token = item['token'];
// SKIP if token is empty or null
if (!hasValidToken(token)) continue;
String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}';
if (seenIds.contains(uniqueId)) continue;
@ -78,7 +98,11 @@ class TokenStorageService {
// Add post-enrollment data
if (_postEnrollmentData != null) {
for (var item in _postEnrollmentData!) {
String token = item['token']?.toString() ?? '';
final token = item['token'];
// SKIP if token is empty or null
if (!hasValidToken(token)) continue;
String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}';
if (seenIds.contains(uniqueId)) continue;
@ -94,42 +118,41 @@ class TokenStorageService {
return combined;
}
// Save selected branch and decode token
Future<void> saveSelectedBranch(Map<String, dynamic> branch) async {
// 🌿 Save selected branch + decode JWT
Future<void> saveSelectedBranch(
Map<String, dynamic> branch) async {
_selectedBranch = branch;
String token = branch['token']?.toString() ?? '';
if (token.isNotEmpty) {
_decodedToken = _decodeJWT(token);
} else {
_decodedToken = null;
}
_decodedToken = token.isNotEmpty ? _decodeJWT(token) : null;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_selectedBranchKey, json.encode(branch));
await _secureStorage.write(
key: _selectedBranchKey, value: json.encode(branch));
if (_decodedToken != null) {
await prefs.setString(_decodedTokenKey, json.encode(_decodedToken));
await _secureStorage.write(
key: _decodedTokenKey,
value: json.encode(_decodedToken));
}
// Save branch name
String branchName = branch['branch_name']?.toString() ?? '';
await prefs.setString('branchName', branchName);
await _secureStorage.write(
key: _branchNameKey,
value: branch['branch_name']?.toString() ?? '',
);
}
// Decode JWT token
// 🔓 Decode JWT
Map<String, dynamic>? _decodeJWT(String token) {
try {
final parts = token.split('.');
if (parts.length != 3) return null;
final payload = parts[1];
var normalized = base64Url.normalize(payload);
var decoded = utf8.decode(base64Url.decode(normalized));
final payload = base64Url.normalize(parts[1]);
final decoded = utf8.decode(base64Url.decode(payload));
return json.decode(decoded);
} catch (e) {
print('Error decoding token: $e');
print('JWT decode error: $e');
return null;
}
}
@ -139,23 +162,131 @@ class TokenStorageService {
Map<String, dynamic>? getDecodedToken() => _decodedToken;
String? getCurrentToken() => _selectedBranch?['token'];
bool isLoggedIn() {
return _selectedBranch != null &&
_selectedBranch!['token'] != null &&
_selectedBranch!['token'].toString().isNotEmpty;
}
bool isLoggedIn() =>
_selectedBranch != null &&
_selectedBranch!['token'] != null &&
_selectedBranch!['token'].toString().isNotEmpty;
// Clear all data (logout)
// 🚪 Logout (clear everything)
Future<void> clearAll() async {
_preEnrollmentData = null;
_postEnrollmentData = null;
_selectedBranch = null;
_decodedToken = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_preEnrollmentKey);
await prefs.remove(_postEnrollmentKey);
await prefs.remove(_selectedBranchKey);
await prefs.remove(_decodedTokenKey);
await _secureStorage.deleteAll();
}
}
Future<void> saveDecodedSessionData(
Map<String, dynamic> decodedToken,
String token,
) async {
// Handle allowed_modules safely
dynamic allowedModules = decodedToken['allowed_modules'];
if (allowedModules is String) {
allowedModules = jsonDecode(allowedModules);
}
await _secureStorage.write(
key: 'empClientBranchId',
value: decodedToken['post_branch_id']?.toString());
await _secureStorage.write(
key: 'empPrimaryId',
value: decodedToken['post_hr_id']?.toString());
await _secureStorage.write(
key: 'empClientId',
value: decodedToken['post_client_id']?.toString());
await _secureStorage.write(
key: 'empHrId',
value: decodedToken['post_hr_id']?.toString());
await _secureStorage.write(
key: 'empAllowed_modules',
value: jsonEncode(allowedModules?['post'] ?? []));
// ================= PRE (Enrollment) =================
await _secureStorage.write(
key: 'enrollmentEmpClientBranchId',
value: decodedToken['pre_branch_id']?.toString());
await _secureStorage.write(
key: 'enrollmentEmpPrimaryId',
value: decodedToken['pre_hr_id']?.toString());
await _secureStorage.write(
key: 'enrollmentClient_id',
value: decodedToken['pre_client_id']?.toString());
await _secureStorage.write(
key: 'enrollmentHrId',
value: decodedToken['pre_hr_id']?.toString());
await _secureStorage.write(
key: 'enrollmentAllowed_modules',
value: jsonEncode(allowedModules?['pre'] ?? []));
// ================= TOKEN =================
await _secureStorage.write(key: 'token', value: token);
}
Future<String?> readValue(String key) async {
return await _secureStorage.read(key: key);
}
Future<void> writeValue(String key, String value) async {
await _secureStorage.write(key: key, value: value);
}
Future<void> clearBranchSession() async {
final keysToRemove = [
'selected_branch',
'decoded_token',
'clientLogo',
'clientName',
'empAllowed_modules',
'empClientBranchId',
'empClientId',
'empEmail',
'empHrId',
'empPrimaryId',
'enrollmentAllowed_modules',
'enrollmentClient_id',
'enrollmentEmpClientBranchId',
'enrollmentEmpPrimaryId',
'enrollmentHrId',
'token',
];
for (final key in keysToRemove) {
await _secureStorage.delete(key: key);
}
}
Future<void> resetSessionAndSwitchBranch(
Map<String, dynamic> newBranch,
) async {
// 1 Clear ONLY branch/session related keys
await clearBranchSession();
// 2 Save selected branch
await saveSelectedBranch(newBranch);
// 3 Rebuild decoded session data from token
final token = newBranch['token']?.toString();
if (token != null && token.isNotEmpty) {
final decoded = _decodeJWT(token);
if (decoded != null) {
await saveDecodedSessionData(decoded, token);
}
}
// 4 Update in-memory cache
_selectedBranch = newBranch;
}
}

View File

@ -6,10 +6,14 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <smart_auth/smart_auth_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) smart_auth_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin");
smart_auth_plugin_register_with_registrar(smart_auth_registrar);

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
smart_auth
url_launcher_linux
)

View File

@ -8,6 +8,7 @@ import Foundation
import file_picker
import firebase_auth
import firebase_core
import flutter_secure_storage_darwin
import google_sign_in_ios
import path_provider_foundation
import shared_preferences_foundation
@ -18,6 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@ -59,6 +59,11 @@ dependencies:
firebase_auth_web: ^5.12.4
archive: ^3.4.9
dropdown_search: ^6.0.2
flutter_secure_storage: ^10.0.0
flutter_svg: ^2.2.3
pdf: ^3.11.3
dotted_border: ^3.1.0
dropdown_button2: ^2.3.9
dev_dependencies:

View File

@ -8,6 +8,7 @@
#include <firebase_auth/firebase_auth_plugin_c_api.h>
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <smart_auth/smart_auth_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
SmartAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
firebase_auth
firebase_core
flutter_secure_storage_windows
smart_auth
url_launcher_windows
)