post_enrollment_app/lib/pages/postEnrollment/policies.dart

2459 lines
103 KiB
Dart
Executable File

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:nhance_app_pwa/customAppBar/customAppBar.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/service/api_service.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/service/svg_service.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../customAppBar/customFooter.dart';
import '../helpers/custom_download_snackbar.dart';
import '../helpers/ecard_download_service.dart';
import '../helpers/ecard_download_notification_service.dart';
import '../../customAppBar/responsive.dart';
import '../../customAppBar/tabs.dart';
import '../../customAppBar/toastHelper.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_svg/flutter_svg.dart';
import '../service/SessionManager.dart';
import '../service/TokenService.dart';
import '../service/popup_helper.dart';
import 'package:nhance_app_pwa/logger.dart';
class policies extends StatefulWidget {
final Map<String, dynamic>? arguments;
const policies({super.key, this.arguments});
@override
State<policies> createState() => _policiesState();
}
class _policiesState extends State<policies> {
late ApiService apiService;
final EcardDownloadService _ecardDownloadService = const EcardDownloadService();
final session = SessionManager();
int _currentIndex = 0;
bool isActive = true;
dynamic _token;
dynamic empCodeString;
dynamic empPrimaryId;
dynamic empName;
dynamic mobileNo;
dynamic client_branch_id;
dynamic client_id;
dynamic policyList;
dynamic policyDataIsEmpty = 1;
dynamic policyHeading;
dynamic policyName;
dynamic EmployeePolicy;
List<Map<String, String>> employeeDetails = [];
dynamic argumentsData;
bool ECardHide = true;
bool _isExpanded = false;
bool _isDownloadingEcard = false;
bool _isLoadingEmployeePolicy = false;
bool _employeePolicyFetched = false;
bool _addButtonShow = false;
List<dynamic> _mappedFamilyFloaters = [];
List<dynamic> _allowedRelationships = [];
dynamic _basicCoverSi;
Map<String, dynamic>? _employeePolicyData;
bool _argsInitialized = false;
final TextEditingController _relationShipController = TextEditingController();
final TextEditingController _memberNameController = TextEditingController();
final TextEditingController _dobController = TextEditingController();
final TextEditingController _effectiveDateController = TextEditingController();
void _onTabChanged(int index) {
setState(() {
_currentIndex = index;
});
}
void _toggleExpansion() {
setState(() {
_isExpanded = !_isExpanded;
});
}
@override
void initState() {
super.initState();
apiService = ApiService(context); // Initialize ApiService here
_loadToken();
EcardDownloadNotificationService.ensureInitialized();
if (!kIsWeb) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _ecardDownloadService.ensureStorageAccess();
});
}
}
@override
void dispose() {
_relationShipController.dispose();
_memberNameController.dispose();
_dobController.dispose();
_effectiveDateController.dispose();
super.dispose();
}
Future<void> _loadToken() async {
logDebug('_loadToken');
final String? token = await TokenService.getPostToken();
_token = token;
if (token != null && token.isNotEmpty) {
mobileNo = session.mobileNo;
client_branch_id = session.empClientBranchId;
empCodeString = session.empCodeString;
empName = session.gpaEmpName;
logDebug(empCodeString);
empPrimaryId = session.empPrimaryId;
client_id = session.client_id;
await _fetchEmployeePolicyIfNeeded();
}
}
bool _isGmcOrOpdPolicy(String? policyType) {
final type = (policyType ?? '').trim().toUpperCase();
return type == 'GMC' || type == 'OPD' || type == 'GMC - OPD';
}
void _applyEmployeePolicyList(List? policyList) {
employeeDetails.clear();
if (policyList == null) return;
for (final employee in policyList) {
if (employee is! Map) continue;
employeeDetails.add({
'id': employee['id']?.toString() ??
employee['employee_id']?.toString() ??
'',
'name': employee['name']?.toString() ?? '',
'relationship': employee['relationship']?.toString() ?? '',
'dob': employee['dob']?.toString() ?? '',
'gender': employee['gender']?.toString() ?? '',
if (employee['emp_status'] != null)
'emp_status': employee['emp_status'].toString(),
if (employee['status'] != null) 'status': employee['status'].toString(),
if (employee['basic_cover_si'] != null)
'basic_cover_si': employee['basic_cover_si'].toString(),
if (employee['dependent_effective_date'] != null)
'dependent_effective_date':
employee['dependent_effective_date'].toString(),
if (employee['date_coverage'] != null)
'date_coverage': employee['date_coverage'].toString(),
});
}
sortEmployeeDetails(employeeDetails);
}
Future<void> _fetchEmployeePolicyIfNeeded({bool force = false}) async {
final args = widget.arguments;
if (args == null || !_isGmcOrOpdPolicy(args['policy_type']?.toString())) {
return;
}
if (!force && (_isLoadingEmployeePolicy || _employeePolicyFetched)) return;
final clientPolicyId = args['client_policy_id']?.toString();
if (empPrimaryId == null ||
empCodeString == null ||
client_id == null ||
client_branch_id == null ||
clientPolicyId == null ||
clientPolicyId.isEmpty) {
logDebug('getEmployeePolicy skipped: missing required params');
return;
}
if (!mounted) return;
setState(() {
_isLoadingEmployeePolicy = true;
if (force) _employeePolicyFetched = false;
});
try {
final response = await apiService.getEmployeePolicy(
id: empPrimaryId.toString(),
empCode: empCodeString.toString(),
clientId: client_id.toString(),
clientBranchId: client_branch_id.toString(),
clientPolicyId: clientPolicyId,
);
if (!mounted) return;
if (response['status'] == 'success') {
final data = response['data'];
setState(() {
EmployeePolicy = response['EmployeePolicy'] ?? [];
_addButtonShow = response['add_button_show'] == true;
if (data is Map) {
_employeePolicyData = Map<String, dynamic>.from(data);
_mappedFamilyFloaters = data['mapped_family_floaters'] is List
? List.from(data['mapped_family_floaters'])
: [];
_allowedRelationships = data['allowed_relationships'] is List
? List.from(data['allowed_relationships'])
: (data['relationship'] is List
? List.from(data['relationship'])
: []);
_basicCoverSi = _resolveBasicCoverSi(data);
}
_applyEmployeePolicyList(
EmployeePolicy is List ? EmployeePolicy as List : null,
);
_employeePolicyFetched = true;
_isLoadingEmployeePolicy = false;
});
logDebug('getEmployeePolicy success; add_button_show=$_addButtonShow');
} else {
setState(() {
_isLoadingEmployeePolicy = false;
_employeePolicyFetched = true;
});
final message = response['message']?.toString() ??
response['data']?.toString() ??
'Failed to load employee policy';
ToastHelper.showErrorToast(context, message);
logDebug('getEmployeePolicy failed: $message');
}
} catch (e) {
logDebug('getEmployeePolicy exception: $e');
if (mounted) {
setState(() {
_isLoadingEmployeePolicy = false;
_employeePolicyFetched = true;
});
ToastHelper.showErrorToast(
context,
'Unable to load dependent details. Please try again.',
);
}
}
}
dynamic _resolveBasicCoverSi(Map data) {
if (data['family_floaters_of_dependent_and_si_value'] != null) {
return data['family_floaters_of_dependent_and_si_value'];
}
if (data['si_value'] != null) return data['si_value'];
if (data['sum_insured'] != null) return data['sum_insured'];
if (data['Policy_Terms'] is Map &&
data['Policy_Terms']['sum_insured'] != null) {
return data['Policy_Terms']['sum_insured'];
}
final floaters = data['mapped_family_floaters'];
if (floaters is List) {
for (final floater in floaters) {
if (floater is Map &&
floater['is_value_exist'] == true &&
floater['data'] is Map &&
floater['data']['basic_cover_si'] != null) {
return floater['data']['basic_cover_si'];
}
}
}
return argumentsData != null ? argumentsData['si_value'] : null;
}
bool _isSpouseRelationship(String? relationship) =>
(relationship ?? '').trim().toLowerCase() == 'spouse';
bool _isChildRelationship(String? relationship) {
final value = (relationship ?? '').trim().toLowerCase();
return value == 'son' || value == 'daughter' || value == 'child';
}
bool _isWithinLast30Days(DateTime date) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final selected = DateTime(date.year, date.month, date.day);
final diff = today.difference(selected).inDays;
return !selected.isAfter(today) && diff <= 30;
}
String _thirtyDayRuleMessage(String? relationship) {
if (_isSpouseRelationship(relationship)) {
return 'A dependent can only be added within 30 days of the Marriage Date';
}
return 'A dependent can only be added within 30 days of the Birth Date';
}
/// Relationship options from empty family slots only (`is_value_exist == false`).
List<Map<String, dynamic>> _availableSlotRelationships() {
final result = <Map<String, dynamic>>[];
final seen = <String>{};
for (final floater in _mappedFamilyFloaters) {
if (floater is! Map || floater['is_value_exist'] != false) continue;
final data = floater['data'];
if (data is! Map) continue;
final List<String> relationships = [];
if (data['allowed_relationships'] is List) {
for (final item in data['allowed_relationships'] as List) {
final name = item?.toString().trim() ?? '';
if (name.isNotEmpty) relationships.add(name);
}
} else {
final formType = (data['form_type'] ?? '').toString().toLowerCase();
if (formType == 'spouse') {
relationships.add('Spouse');
} else if (formType == 'child') {
relationships.addAll(['Son', 'Daughter']);
}
}
for (final relationship in relationships) {
final key = relationship.toLowerCase();
if (seen.contains(key)) continue;
seen.add(key);
result.add({
'relationship': relationship,
'age_validation':
data['age_validation'] ?? {'min': '0', 'max': '99'},
'form_type': data['form_type'],
'family_floater_key': data['family_floater_key'],
'client_policy_id': data['client_policy_id'],
'basic_cover_si': data['basic_cover_si'] ?? _basicCoverSi,
});
}
}
return result;
}
String _formatDateForApi(DateTime date) =>
DateFormat('dd/MM/yyyy').format(date);
DateTime? _parseApiDate(String raw) {
if (raw.trim().isEmpty) return null;
try {
if (raw.contains('/')) {
return DateFormat('dd/MM/yyyy').parseStrict(raw);
}
if (raw.contains('-') && raw.length == 10 && raw[4] == '-') {
return DateTime.parse(raw);
}
if (raw.contains('-')) {
return DateFormat('dd-MM-yyyy').parseStrict(raw);
}
return DateFormat('d MMM yyyy').parseStrict(raw);
} catch (_) {
return null;
}
}
String _normalizeDobForDisplay(String raw) {
final parsed = _parseApiDate(raw);
if (parsed == null) return raw;
return _formatDateForApi(parsed);
}
Future<void> _pickDate({
required TextEditingController controller,
Map<String, dynamic>? relationshipData,
bool isMarriageDate = false,
bool restrictToLastThirtyDays = false,
String? thirtyDayToastMessage,
}) async {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
DateTime initial = today;
DateTime first = DateTime(1900);
DateTime last = today;
if (isMarriageDate || restrictToLastThirtyDays) {
// Spouse marriage date / Child DOB: only allow last 30 days.
first = today.subtract(const Duration(days: 30));
last = today;
initial = today;
} else if (relationshipData != null) {
final age = relationshipData['age_validation'];
if (age is Map) {
final min = int.tryParse(age['min']?.toString() ?? '') ?? 0;
final max = int.tryParse(age['max']?.toString() ?? '') ?? 100;
last = DateTime(now.year - min, now.month, now.day);
first = DateTime(now.year - max, now.month, now.day);
initial = last;
}
}
if (controller.text.isNotEmpty) {
final existing = _parseApiDate(controller.text);
if (existing != null) initial = existing;
}
final picked = await showDatePicker(
context: context,
initialDate: initial.isBefore(first)
? first
: (initial.isAfter(last) ? last : initial),
firstDate: first,
lastDate: last.isBefore(first) ? first : last,
);
if (picked == null) return;
if ((isMarriageDate || restrictToLastThirtyDays) &&
!_isWithinLast30Days(picked)) {
ToastHelper.showWarningToast(
context,
thirtyDayToastMessage ??
(isMarriageDate
? 'A dependent can only be added within 30 days of the Marriage Date'
: 'A dependent can only be added within 30 days of the Birth Date'),
);
return;
}
controller.text = _formatDateForApi(picked);
}
void _onAddFamilyMember() {
_openDependentForm(action: 'Add');
}
void _onEditFamilyMember(Map<String, String> detail) {
_openDependentForm(action: 'Edit', detail: detail);
}
String _resolveCoverageDateForDetail(Map<String, String> detail) {
final directCoverageDate = detail['date_coverage']?.toString().trim() ?? '';
if (directCoverageDate.isNotEmpty) return directCoverageDate;
final directDependentDate =
detail['dependent_effective_date']?.toString().trim() ?? '';
if (directDependentDate.isNotEmpty) return directDependentDate;
final detailId = detail['id']?.toString().trim() ?? '';
final detailRelationship =
(detail['relationship']?.toString().trim().toLowerCase()) ?? '';
for (final floater in _mappedFamilyFloaters) {
if (floater is! Map || floater['is_value_exist'] != true) continue;
final data = floater['data'];
if (data is! Map) continue;
final floaterId = data['employee_id']?.toString().trim() ?? '';
final floaterRelationship =
(data['relationship']?.toString().trim().toLowerCase()) ?? '';
final idMatches = detailId.isNotEmpty && floaterId == detailId;
final relationMatches =
detailRelationship.isNotEmpty && floaterRelationship == detailRelationship;
if (!idMatches && !relationMatches) continue;
final coverage = data['date_coverage']?.toString().trim() ?? '';
if (coverage.isNotEmpty) return coverage;
final dependentDate =
data['dependent_effective_date']?.toString().trim() ?? '';
if (dependentDate.isNotEmpty) return dependentDate;
}
return '';
}
Future<void> _onDeleteFamilyMember(Map<String, String> detail) async {
final id = detail['id'] ?? '';
final relationship = (detail['relationship'] ?? '').toLowerCase();
if (id.isEmpty || relationship == 'self') {
ToastHelper.showWarningToast(context, 'Cannot delete this member');
return;
}
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(
'Delete Dependent',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
content: Text(
'Are you sure you want to delete ${detail['name']}?',
style: GoogleFonts.poppins(fontSize: 14),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Cancel'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728),
),
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Delete', style: TextStyle(color: Colors.white)),
),
],
),
);
if (confirmed != true) return;
try {
final response = await apiService.deleteDependence(id);
if (!mounted) return;
if (response['status'] == 'success') {
ToastHelper.showSuccessToast(
context,
response['message']?.toString() ?? 'Dependent deleted successfully',
);
await _fetchEmployeePolicyIfNeeded(force: true);
} else {
ToastHelper.showErrorToast(
context,
response['message']?.toString() ??
response['data']?.toString() ??
'Failed to delete dependent',
);
}
} catch (e) {
logDebug('deleteDependence exception: $e');
if (mounted) {
ToastHelper.showErrorToast(
context,
'Unable to delete dependent. Please try again.',
);
}
}
}
void _openDependentForm({
required String action,
Map<String, String>? detail,
}) {
final relationshipObjects = action == 'Add'
? _availableSlotRelationships()
: <Map<String, dynamic>>[];
String? dropdownValue;
Map<String, dynamic>? selectedRelationship;
_relationShipController.clear();
_memberNameController.clear();
_dobController.clear();
_effectiveDateController.clear();
if (action == 'Edit' && detail != null) {
dropdownValue = detail['relationship'];
_relationShipController.text = detail['relationship'] ?? '';
_memberNameController.text = detail['name'] ?? '';
_dobController.text = _normalizeDobForDisplay(detail['dob'] ?? '');
if (_isSpouseRelationship(dropdownValue)) {
final coverageDate = _resolveCoverageDateForDetail(detail);
_effectiveDateController.text = _normalizeDobForDisplay(
coverageDate,
);
}
selectedRelationship = {
'relationship': dropdownValue,
'age_validation': {'min': '0', 'max': '99'},
'basic_cover_si': detail['basic_cover_si'] ?? _basicCoverSi,
};
relationshipObjects.add(selectedRelationship);
}
if (relationshipObjects.isEmpty && action == 'Add') {
ToastHelper.showWarningToast(
context,
'No family slots available to add',
);
return;
}
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) {
bool isSaving = false;
return StatefulBuilder(
builder: (context, setDialogState) {
final isSpouse = _isSpouseRelationship(dropdownValue);
final isChild = _isChildRelationship(dropdownValue);
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
content: SizedBox(
width: Responsive.isDesktop(context) ? 500 : 350,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
action == 'Edit'
? 'Edit Family Member'
: 'Add Family Member',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.bold,
color: const Color(0xFFE26728),
),
),
const SizedBox(height: 20),
DropdownButtonFormField<String>(
value: dropdownValue,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Relationship',
),
items: relationshipObjects.map((item) {
final value = item['relationship']?.toString() ?? '';
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: action == 'Edit'
? null
: (value) {
setDialogState(() {
dropdownValue = value;
_relationShipController.text = value ?? '';
selectedRelationship =
relationshipObjects.firstWhere(
(item) => item['relationship'] == value,
);
_dobController.clear();
_effectiveDateController.clear();
});
},
),
const SizedBox(height: 15),
TextFormField(
controller: _memberNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Member Name As Per Govt Id Proof',
),
),
const SizedBox(height: 15),
TextFormField(
controller: _dobController,
readOnly: true,
onTap: isSaving || dropdownValue == null
? null
: () async {
await _pickDate(
controller: _dobController,
relationshipData: selectedRelationship,
restrictToLastThirtyDays: isChild,
);
setDialogState(() {});
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'Date of Birth',
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: isSaving || dropdownValue == null
? null
: () async {
await _pickDate(
controller: _dobController,
relationshipData: selectedRelationship,
restrictToLastThirtyDays: isChild,
);
setDialogState(() {});
},
),
),
),
if (isSpouse) ...[
const SizedBox(height: 15),
TextFormField(
controller: _effectiveDateController,
readOnly: true,
onTap: isSaving
? null
: () async {
await _pickDate(
controller: _effectiveDateController,
isMarriageDate: true,
thirtyDayToastMessage:
_thirtyDayRuleMessage(dropdownValue),
);
setDialogState(() {});
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'Date of Wedding',
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: isSaving
? null
: () async {
await _pickDate(
controller: _effectiveDateController,
isMarriageDate: true,
thirtyDayToastMessage:
_thirtyDayRuleMessage(
dropdownValue),
);
setDialogState(() {});
},
),
),
),
],
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: isSaving
? null
: () => Navigator.pop(dialogContext),
child: const Text('Cancel'),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728),
),
onPressed: isSaving
? null
: () async {
if (dropdownValue == null ||
_memberNameController.text
.trim()
.isEmpty ||
_dobController.text.trim().isEmpty ||
(isSpouse &&
_effectiveDateController.text
.trim()
.isEmpty)) {
ToastHelper.showWarningToast(
context,
'All fields are required',
);
return;
}
final dob = _parseApiDate(
_dobController.text.trim());
if (isChild &&
(dob == null ||
!_isWithinLast30Days(dob))) {
ToastHelper.showWarningToast(
context,
_thirtyDayRuleMessage(dropdownValue),
);
return;
}
if (isSpouse) {
final marriageDate = _parseApiDate(
_effectiveDateController.text.trim(),
);
if (marriageDate == null ||
!_isWithinLast30Days(
marriageDate)) {
ToastHelper.showWarningToast(
context,
_thirtyDayRuleMessage(
dropdownValue),
);
return;
}
}
setDialogState(() => isSaving = true);
final saved = await _saveDependent(
action: action,
relationship: dropdownValue!,
detail: detail,
selectedSlot: selectedRelationship,
);
if (!dialogContext.mounted) return;
setDialogState(() => isSaving = false);
if (saved) {
Navigator.pop(dialogContext);
}
},
child: isSaving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'Save',
style: TextStyle(color: Colors.white),
),
),
),
],
),
],
),
),
),
);
},
);
},
);
}
Future<bool> _saveDependent({
required String action,
required String relationship,
Map<String, String>? detail,
Map<String, dynamic>? selectedSlot,
}) async {
final clientPolicyId = selectedSlot?['client_policy_id']?.toString() ??
_employeePolicyData?['ClientPolicyId']?.toString() ??
widget.arguments?['client_policy_id']?.toString() ??
'';
if (empCodeString == null ||
client_id == null ||
client_branch_id == null ||
clientPolicyId.isEmpty) {
ToastHelper.showErrorToast(context, 'Missing required policy details');
return false;
}
final si = selectedSlot?['basic_cover_si'] ??
detail?['basic_cover_si'] ??
_basicCoverSi ??
argumentsData?['si_value'] ??
0;
final dependentEffectiveDate = _isSpouseRelationship(relationship)
? _effectiveDateController.text.trim()
: _dobController.text.trim();
final body = <String, dynamic>{
'emp_code': empCodeString,
'client_id': int.tryParse(client_id.toString()) ?? client_id,
'client_branch_id':
int.tryParse(client_branch_id.toString()) ?? client_branch_id,
'client_policy_id': int.tryParse(clientPolicyId) ?? clientPolicyId,
'name': _memberNameController.text.trim(),
'relationship': relationship,
'dob': _dobController.text.trim(),
'basic_cover_si': int.tryParse(si.toString()) ?? si,
'dependent_effective_date': dependentEffectiveDate,
if (action == 'Edit' && (detail?['id']?.isNotEmpty ?? false))
'id': int.tryParse(detail!['id']!) ?? detail['id'],
};
try {
final response = await apiService.addEmployeeAndDependence([body]);
if (!mounted) return false;
if (response['status'] == 'success') {
ToastHelper.showSuccessToast(
context,
response['message']?.toString() ??
'Dependent submitted for approval',
);
await _fetchEmployeePolicyIfNeeded(force: true);
return true;
}
ToastHelper.showErrorToast(
context,
response['message']?.toString() ??
response['data']?.toString() ??
'Failed to save dependent',
);
return false;
} catch (e) {
logDebug('addEmployeeAndDependence exception: $e');
if (mounted) {
ToastHelper.showErrorToast(
context,
'Unable to save dependent. Please try again.',
);
}
return false;
}
}
Future<void> getEcardDownload(id) async {
if (_isDownloadingEcard) return;
if (!mounted) return;
setState(() => _isDownloadingEcard = true);
try {
final clientPolicyID = widget.arguments?['client_policy_id'];
final policyNo = widget.arguments?['policy_no'];
final eCarDParams = {
'id': id,
'emp_code': empCodeString,
'client_policy_id': clientPolicyID,
'policy_no': policyNo,
};
final response = await apiService.getEcardRequest(eCarDParams);
if (!mounted) return;
final data = response['data'];
if (data is! Map) {
ToastHelper.showErrorToast(context, 'Unable to download E-Card');
return;
}
final ecardDownloadUrl = data['eCardDownload'];
final message = data['message']?.toString();
if (ecardDownloadUrl == null || ecardDownloadUrl.toString().isEmpty) {
logDebug('❌ Error: $message');
ToastHelper.showErrorToast(
context,
message ?? 'E-Card is not available',
);
return;
}
final url = ecardDownloadUrl.toString();
logDebug('✅ E-Card URL: $url');
final safePolicyNo = policyNo?.toString().replaceAll(RegExp(r'[^\w\-.]'), '_');
final fileName = safePolicyNo == null || safePolicyNo.isEmpty
? 'ecard.pdf'
: 'ecard_$safePolicyNo.pdf';
if (!kIsWeb) {
CustomDownloadSnackbar.show(
context,
message: 'Downloading eCard...',
);
}
final result = await _ecardDownloadService.downloadEcard(
url: url,
fileName: fileName,
headers: {
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
if (_token != null && _token.toString().isNotEmpty)
'Authorization': 'Bearer ${_token.toString()}',
},
);
if (!mounted) return;
if (result.success) {
if (kIsWeb) {
ToastHelper.showSuccessToast(
context,
result.message ?? 'E-Card downloaded',
);
} else {
CustomDownloadSnackbar.show(
context,
message: result.message ?? 'eCard downloaded successfully',
);
if (result.savedPath != null && result.savedPath!.isNotEmpty) {
await EcardDownloadNotificationService.showDownloadCompleted(
filePath: result.savedPath!,
fileName: fileName,
);
}
}
} else {
if (kIsWeb) {
ToastHelper.showErrorToast(
context,
result.message ?? 'Could not download E-Card',
);
} else {
CustomDownloadSnackbar.show(
context,
message: result.message ?? 'Download failed',
);
}
}
} catch (e) {
logDebug('E-Card download error: $e');
if (mounted) {
ToastHelper.showErrorToast(
context,
'Unable to download E-Card. Please try again.',
);
}
} finally {
if (mounted) setState(() => _isDownloadingEcard = false);
}
}
String convertDateFormat(String date) {
// Define input and output date formats
DateFormat inputFormat = DateFormat('dd-MMM-yyyy');
DateFormat outputFormat = DateFormat('d MMM yyyy');
// Parse the input date string
DateTime parsedDate = inputFormat.parse(date);
// Format the parsed date to the desired output format
String formattedDate = outputFormat.format(parsedDate);
return formattedDate;
}
String formatDob(String dob) {
try {
if (dob.contains('/')) {
final date = DateFormat('dd/MM/yyyy').parseStrict(dob);
return DateFormat('d MMM yyyy').format(date);
}
final date = DateTime.parse(dob); // input format: yyyy-MM-dd
return DateFormat("d MMM yyyy").format(date); // output: 18 Jul 1988
} catch (e) {
return dob; // fallback in case of error
}
}
Future<void> _openUrl(String url) async {
final uri = Uri.tryParse(url);
if (uri == null) {
logDebug('Could not launch URL');
return;
}
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
logDebug('Could not launch URL');
}
}
DateTime _parseDob(String dob) {
try {
// Expected format: dd-MM-yyyy
final parts = dob.split('-');
if (parts.length == 3) {
return DateTime(
int.parse(parts[2]),
int.parse(parts[1]),
int.parse(parts[0]),
);
}
} catch (_) {}
return DateTime(1900); // fallback
}
List<Map<String, String>> sortEmployeeDetails(
List<Map<String, String>> list) {
int priority(String r) {
final rel = r.toLowerCase();
if (rel == 'self') return 1;
if (rel == 'spouse') return 2;
if (rel == 'son' || rel == 'daughter' || rel == 'child') return 3;
if (rel == 'father') return 4;
if (rel == 'mother') return 5;
if (rel == 'father in law') return 6;
if (rel == 'mother in law') return 7;
return 99;
}
list.sort((a, b) {
final p1 = priority(a['relationship'] ?? '');
final p2 = priority(b['relationship'] ?? '');
// main relationship order
if (p1 != p2) return p1.compareTo(p2);
// ONLY children → oldest first
if (p1 == 3) {
final d1 = parseDobSafe(a['dob'] ?? '');
final d2 = parseDobSafe(b['dob'] ?? '');
return d1.compareTo(d2); // older first
}
return 0;
});
return list;
}
DateTime parseDobSafe(String dob) {
try {
// Case 1: yyyy-MM-dd → 2015-05-05
if (dob.contains('-') && dob.length == 10) {
return DateTime.parse(dob);
}
// Case 2: d MMM yyyy → 1 Feb 1996
final parts = dob.split(' ');
final day = int.parse(parts[0]);
final monthMap = {
'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4,
'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8,
'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12,
};
final month = monthMap[parts[1]]!;
final year = int.parse(parts[2]);
return DateTime(year, month, day);
} catch (e) {
// fallback → push invalid dates to bottom
return DateTime(3000);
}
}
@override
Widget build(BuildContext context) {
final arguments = widget.arguments;
logDebug('arguments');
logDebug(arguments);
if (arguments != null && arguments is Map<String, dynamic>) {
argumentsData = arguments;
if (!_argsInitialized) {
_argsInitialized = true;
final policyType = argumentsData['policy_type']?.toString();
ECardHide = policyType == 'GMC' ||
policyType == 'GMC - Parents' ||
policyType == 'GMC - Topup' ||
policyType == 'GMC - Topup(Parents)';
// Seed from route args until getEmployeePolicy returns (GMC/OPD),
// or keep args as source of truth for other policy types.
if (!_employeePolicyFetched &&
argumentsData.containsKey('EmployeePolicy') &&
argumentsData['EmployeePolicy'] is List) {
_applyEmployeePolicyList(argumentsData['EmployeePolicy'] as List);
}
if (_isGmcOrOpdPolicy(policyType) &&
!_employeePolicyFetched &&
!_isLoadingEmployeePolicy) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_fetchEmployeePolicyIfNeeded();
});
}
}
logDebug('employeeDetails');
logDebug(employeeDetails);
}
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
// context.go('/home');
context.pop();
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: CustomAppBar(),
body: Stack(children: [
SingleChildScrollView(
child: Container(
padding: Responsive.isDesktop(context)
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.2, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0.03, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
color:
Responsive.isDesktop(context) ? Colors.white : Color(0xFFFFFCE5),
child: Column(children: [
Container(
decoration: BoxDecoration(
color:
Color(0xFFFFFCE5), // Set background color for the container
borderRadius: BorderRadius.circular(
10), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(top: 20, bottom: 20, left: 25, right: 25)
: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: InkWell(
onTap: () {
// context.go('/home');
context.pop();
},
child: Icon(
Icons
.chevron_left, // Replace with your desired icon
color: Color(0xFF000000),
size: 30,
),
),
),
Expanded(
flex: 11,
child: Row(
mainAxisAlignment: Responsive.isDesktop(context)
? MainAxisAlignment.start
: MainAxisAlignment.start,
children: [
Text(
argumentsData['policy_name'] ?? '',
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context) ? 20 : 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
),
)
],
),
)
],
),
SizedBox(
height: Responsive.isDesktop(context) ? 20 : 16),
_buildPolicyDetailsSection(context),
SizedBox(
height: Responsive.isDesktop(context) ? 32 : 24),
_buildPolicyActionButtons(context),
SizedBox(height: Responsive.isDesktop(context) ? 30 : 20),
// Add more rows as needed
],
),
),
Container(
decoration: BoxDecoration(
color: Colors.white, // Set background color for the container
borderRadius: BorderRadius.only(
topLeft: Radius.circular(40.0),
topRight: Radius.circular(40.0),
), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(top: 15, bottom: 15, left: 0, right: 0)
: EdgeInsets.only(top: 20, bottom: 10, left: 10, right: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Color(0xFFD9D9D9), // Set the border color here
width: 1.0, // Set the border width here
),
borderRadius: BorderRadius.circular(
8.0), // Set the border radius here
),
child: Column(children: [
Container(
decoration: BoxDecoration(
color: Color(
0xFFECF2FF), // Set background color for the container
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(
10)), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 15, bottom: 15, left: 15, right: 15)
: EdgeInsets.only(
top: 20, bottom: 20, left: 10, right: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 12,
child: Container(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Insured Members',
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 18
: 12,
color: Color(0xFF404040),
fontWeight: FontWeight.w400,
),
),
],
))),
],
),
),
Container(
decoration: BoxDecoration(
color: Colors.white,
),
padding: Responsive.isDesktop(context)
? EdgeInsets.all(30)
: EdgeInsets.only(
top: 15, bottom: 15, left: 20, right: 20),
child: Column(
children: [
if (_isLoadingEmployeePolicy)
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Color(0xFFE26728),
),
),
),
),
...employeeDetails.map((detail) {
int index = employeeDetails.indexOf(detail);
final isPending =
(detail['emp_status'] ?? '')
.toLowerCase() ==
'pending_approval';
final isSelf =
(detail['relationship'] ?? '')
.toLowerCase() ==
'self';
final canManageDependent = _employeePolicyFetched &&
_isGmcOrOpdPolicy(
argumentsData != null
? argumentsData['policy_type']
?.toString()
: null,
) &&
!isSelf &&
isPending;
return Container(
decoration: BoxDecoration(
border: Border(
bottom:
index != employeeDetails.length - 1
? BorderSide(
color: Color(0xFFD9D9D9),
width:
1.0, // Adjust the width as needed
)
: BorderSide.none,
),
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Expanded(
flex: 6,
child: Container(
alignment: Alignment.centerLeft,
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10)
: EdgeInsets.only(
top: 15,
bottom: 15,
left: 7,
right: 7),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
// 👇 SVG icon based on gender
if (detail['gender'] == 'M')
SvgPicture.string(
SvgService.getSvg('personMale'),
width: 25,
height: 25,
)
else if (detail['gender'] == 'F')
SvgPicture.string(
SvgService.getSvg('personFemale'),
width: 25,
height: 25,
),
const SizedBox(width: 6),
// Name text
Flexible(
child: Text(
detail['name']!,
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 16 : 14,
color: const Color(0xFF000000),
fontWeight: FontWeight.w400,
),
),
),
if (isPending) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF1DD),
borderRadius:
BorderRadius.circular(4),
),
child: Text(
'Pending',
style: GoogleFonts.poppins(
fontSize: 10,
color: const Color(0xFFE26728),
fontWeight: FontWeight.w500,
),
),
),
],
],
),
const SizedBox(height: 4),
// DOB text
Text(
"DOB: ${formatDob(detail['dob']!) ?? '-'}",
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 14 : 12,
color: const Color(0xFF606060),
fontWeight: FontWeight.w300,
),
),
],
)
),
),
Expanded(
flex: 6,
child: Container(
alignment: Alignment.centerRight,
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 10,
bottom: 10,
left: 10,
right: 10)
: EdgeInsets.only(
top: 15,
bottom: 15,
left: 7,
right: 7),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
if (canManageDependent) ...[
MouseRegion(
cursor: SystemMouseCursors
.click,
child: GestureDetector(
onTap: () =>
_onEditFamilyMember(
detail),
child: const Tooltip(
message: 'Edit',
child: Icon(
Icons.edit,
size: 20,
color: Color(
0xFFE26728),
),
),
),
),
const SizedBox(width: 10),
MouseRegion(
cursor: SystemMouseCursors
.click,
child: GestureDetector(
onTap: () =>
_onDeleteFamilyMember(
detail),
child: const Tooltip(
message: 'Delete',
child: Icon(
Icons.delete,
size: 20,
color: Color(
0xFFE26728),
),
),
),
),
],
if (ECardHide &&
!isPending) ...[
if (canManageDependent)
const SizedBox(width: 10),
MouseRegion(
cursor: SystemMouseCursors
.click,
child: GestureDetector(
onTap:
_isDownloadingEcard
? null
: () {
getEcardDownload(
detail[
'id']);
},
child: Tooltip(
message: 'E-Card',
waitDuration:
const Duration(
milliseconds:
300),
showDuration:
const Duration(
seconds: 2),
child:
SvgPicture.string(
SvgService.getSvg(
'ECard'),
width: 25,
height: 25,
),
),
),
),
],
],
),
if (canManageDependent ||
(ECardHide && !isPending))
const SizedBox(height: 4),
Text(
detail['relationship']!,
textAlign: TextAlign.end,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 16
: 14,
color: Color(0xFF404040),
fontWeight: FontWeight.w400,
),
),
],
)
),
),
],
));
}).toList(),
if (_addButtonShow &&
_isGmcOrOpdPolicy(
argumentsData != null
? argumentsData['policy_type']
?.toString()
: null)) ...[
if (employeeDetails.isNotEmpty)
const SizedBox(height: 12),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: _onAddFamilyMember,
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
vertical: Responsive.isDesktop(context)
? 14
: 12,
horizontal: 12,
),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFF000000),
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Icon(
Icons.person_add,
color: Color(0xFF000000),
size: 22,
),
const SizedBox(width: 10),
Text(
'Add Family Member',
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 16
: 14,
fontWeight: FontWeight.w500,
color: const Color(0xFF000000),
),
),
],
),
),
),
),
],
],
),
)
])),
SizedBox(height: 10),
Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Color(0xFFD9D9D9), // Set the border color here
width: 1.0, // Set the border width here
),
borderRadius: BorderRadius.circular(
8.0), // Set the border radius here
),
child: Column(children: [
InkWell(
onTap: _toggleExpansion,
child: Container(
decoration: BoxDecoration(
color: Colors
.white, // Set background color for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.all(15)
: EdgeInsets.symmetric(
vertical: 20, horizontal: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 8,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.string(
SvgService.getSvg('policyCoverage'),
width: 25,
height: 25,
),
SizedBox(
width:
10), // Adjust space between icon and text
Text(
'Policy coverage',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 18
: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF404040),
),
),
],
),
),
Expanded(
flex: 4,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
_isExpanded ? Icons.remove : Icons.add,
color: Color(0xFFE26728),
size: 25,
),
],
),
),
],
),
),
),
if (_isExpanded)
Builder(
builder: (context) {
final terms = argumentsData['policy_terms'];
if (terms == null || (terms is List && terms.isEmpty) || (terms is Map && terms.isEmpty)) {
// ✅ Show toast once
WidgetsBinding.instance.addPostFrameCallback((_) {
ToastHelper.showInfoToast(context, 'No policy terms available');
});
return const SizedBox(); // Don't render container
}
return Container(
decoration: const BoxDecoration(
color: Colors.white,
),
padding: Responsive.isDesktop(context)
? const EdgeInsets.all(30)
: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: (() {
if (terms is Map) {
// ✅ Case 1: Map<String, dynamic>
return terms.entries
.where((entry) => entry.value is String)
.toList()
.asMap()
.entries
.map<Widget>((entry) {
int index = entry.key;
MapEntry value = entry.value;
bool isLast = index == terms.length - 1;
return Container(
width: double.infinity,
decoration: BoxDecoration(
border: isLast
? null
: const Border(
bottom: BorderSide(
color: Color(0xFFD9D9D9),
width: 1.0,
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Responsive.isDesktop(context)
? Row(
children: [
Expanded(
flex: 6,
child: Text(
value.key,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: const Color(0xFF777777),
),
),
),
Expanded(
flex: 6,
child: Text(
value.value,
textAlign: TextAlign.end,
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w400,
color: const Color(0xFF000000),
),
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value.key,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: const Color(0xFF777777),
),
),
const SizedBox(height: 4),
Text(
value.value,
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w400,
color: const Color(0xFF000000),
),
),
],
),
),
);
}).toList();
} else if (terms is List) {
// ✅ Case 2: List
return terms.map<Widget>((item) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Text(
item.toString(),
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w400,
color: const Color(0xFF000000),
),
),
);
}).toList();
} else {
return [const SizedBox()];
}
})(),
),
),
);
},
),
])),
SizedBox(height: 10),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => {
context.push('/generalExclusionsDeductibles')
},
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color:
Color(0xFFD9D9D9), // Set the border color here
width: 1.0, // Set the border width here
),
borderRadius: BorderRadius.circular(
8.0), // Set the border radius here
),
child: Column(children: [
Container(
decoration: BoxDecoration(
color: Colors
.white, // Set background color for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 15, bottom: 15, left: 15, right: 15)
: EdgeInsets.only(
top: 20, bottom: 20, left: 10, right: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 11,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.string(
SvgService.getSvg('general'),
width: 25,
height: 25,
),
SizedBox(
width:
10), // Adjust space between icon and text
Text(
'General Exclusions & Deductibles',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 18
: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF404040),
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
Icons
.chevron_right, // Replace with your desired icon
color: Color(0xFFE26728),
size: 20,
),
],
),
),
],
),
),
]),
),
),
),
SizedBox(height: 10),
Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Color(0xFFD9D9D9), // Set the border color here
width: 1.0, // Set the border width here
),
borderRadius: BorderRadius.circular(
8.0), // Set the border radius here
),
child: Column(children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
// Set background color for the container
borderRadius: BorderRadius.circular(
5), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 15, bottom: 15, left: 25, right: 25)
: EdgeInsets.only(
top: 10, bottom: 10, left: 10, right: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 6,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
context.push('/help');
},
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
SvgPicture.string(
SvgService.getSvg('needHelp'),
width: 25,
height: 25,
),
SizedBox(height: 10),
Text(
'Need Help',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 14
: 10,
fontWeight: FontWeight.w400,
color: Color(0xFF404040),
),
),
],
),
),
),
),
),
Expanded(
flex: 6,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
context.push('/claims', extra: 0);
},
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
SvgPicture.string(
SvgService.getSvg(
'cliamHistory'),
width: 25,
height: 25,
),
SizedBox(height: 10),
Text(
'Claims history',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(
context)
? 14
: 10,
fontWeight: FontWeight.w400,
color: Color(0xFF404040),
),
),
],
))),
),
),
],
),
),
])),
SizedBox(height: 10),
if (argumentsData['network_hospitals_url'] != null)
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
final String? url =
argumentsData['network_hospitals_url'];
if (url != null && url.isNotEmpty) {
_openUrl(url);
} else {
logDebug('No URL provided');
}
},
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Color(0xFFD9D9D9),
width: 1.0,
),
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 15,
bottom: 15,
left: 15,
right: 15)
: EdgeInsets.only(
top: 20,
bottom: 20,
left: 10,
right: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 11,
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
SvgPicture.string(
SvgService.getSvg('cashLess'),
width: 25,
height: 25,
),
SizedBox(width: 10),
Text(
'Cashless Hospitals',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context)
? 18
: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF404040),
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
Icon(
Icons.chevron_right,
color: Color(0xFFE26728),
size: 20,
),
],
),
),
],
),
),
],
),
),
),
),
SizedBox(height: Responsive.isDesktop(context) ? 40 : 70),
],
),
),
]),
)),
if (Responsive.isDesktop(context))
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity, // Make the footer full width
child: CustomFooter(),
),
),
]),
// floatingActionButton: Responsive.isDesktop(context)
// ? null
// : FloatingActionButton(
// onPressed: () {
// Navigator.pushNamed(context, 'chatbot');
// },
// child: Icon(Icons.chat),
// ),
floatingActionButtonLocation: Responsive.isDesktop(context)
? null
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
if (index == 0) {
context.push('/home');
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
// // context.push('/wellness');
// // Wellness tab clicked → show popup
// if(!isRetailLoggedIn)
// PopupHelper.showRedirectPopup(
// context: context,
// apiService: apiService,
// empPrimaryId: session.empPrimaryId,
// );
// }
},
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)
);
}
TextStyle _policyLabelStyle(BuildContext context) {
return GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 14 : 13,
fontWeight: FontWeight.w400,
color: const Color(0xFF777777),
);
}
TextStyle _policyValueStyle(BuildContext context) {
return GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 18 : 15,
fontWeight: FontWeight.w600,
color: const Color(0xFF000000),
);
}
String _policyFieldValue(dynamic value) {
final text = value?.toString().trim() ?? '';
return text.isEmpty ? '-' : text;
}
String _formattedPolicyExpiry() {
final raw = argumentsData['policy_end_date']?.toString() ?? '';
if (raw.isEmpty) return '-';
try {
return convertDateFormat(raw);
} catch (_) {
return raw;
}
}
String _formattedSiValue() {
final raw = _policyFieldValue(argumentsData['si_value']);
if (raw == '-') return raw;
return '$raw';
}
Widget _buildPolicyDetailField(
BuildContext context,
String label,
String value,
) {
return SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
textAlign: TextAlign.start,
style: _policyLabelStyle(context),
),
SizedBox(height: Responsive.isDesktop(context) ? 8 : 6),
Text(
value,
textAlign: TextAlign.start,
softWrap: true,
style: _policyValueStyle(context),
),
],
),
);
}
Widget _buildPolicyDetailDivider() {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 14),
child: Divider(
height: 1,
thickness: 1,
color: Color(0xFFD9D9D9),
),
);
}
Widget _buildPolicyDetailsCard(BuildContext context, Widget child) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(Responsive.isDesktop(context) ? 24 : 16),
decoration: BoxDecoration(
color: const Color(0xFFFFFCE5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFD9D9D9)),
),
child: child,
);
}
Widget _buildPolicyDetailsSection(BuildContext context) {
final isDesktop = Responsive.isDesktop(context);
final siLabel =
_policyFieldValue(argumentsData['sum_insured_label'] ?? 'Sum Insured');
if (isDesktop) {
return _buildPolicyDetailsCard(
context,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _buildPolicyDetailField(
context,
'Policy No',
_policyFieldValue(argumentsData['policy_no']),
),
),
const SizedBox(width: 24),
Expanded(
child: _buildPolicyDetailField(
context,
siLabel,
_formattedSiValue(),
),
),
const SizedBox(width: 24),
Expanded(
child: _buildPolicyDetailField(
context,
'Policy Expiry',
_formattedPolicyExpiry(),
),
),
],
),
),
_buildPolicyDetailDivider(),
_buildPolicyDetailField(
context,
'Insurer',
_policyFieldValue(argumentsData['insurer_name']),
),
],
),
);
}
return _buildPolicyDetailsCard(
context,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildPolicyDetailField(
context,
'Policy No',
_policyFieldValue(argumentsData['policy_no']),
),
_buildPolicyDetailDivider(),
_buildPolicyDetailField(
context,
siLabel,
_formattedSiValue(),
),
_buildPolicyDetailDivider(),
_buildPolicyDetailField(
context,
'Policy Expiry',
_formattedPolicyExpiry(),
),
_buildPolicyDetailDivider(),
_buildPolicyDetailField(
context,
'Insurer',
_policyFieldValue(argumentsData['insurer_name']),
),
],
),
);
}
Widget _buildPolicyActionButton({
required BuildContext context,
required String label,
required String svgKey,
required VoidCallback? onPressed,
bool expanded = false,
}) {
final button = Material(
elevation: 0,
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFFFF8BF),
child: SizedBox(
height: 48,
width: Responsive.isDesktop(context) ? 220 : double.infinity,
child: TextButton(
onPressed: onPressed,
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.string(
SvgService.getSvg(svgKey),
width: 24,
height: 24,
),
const SizedBox(width: 8),
Flexible(
child: Text(
label,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: Responsive.isDesktop(context) ? 16 : 14,
fontWeight: FontWeight.w600,
color: const Color(0xFF000000),
),
),
),
],
),
),
),
);
if (expanded) {
return Expanded(child: button);
}
return button;
}
Widget _buildPolicyActionButtons(BuildContext context) {
final isDesktop = Responsive.isDesktop(context);
if (isDesktop) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (ECardHide) ...[
_buildPolicyActionButton(
context: context,
label: 'E-Card',
svgKey: 'ECard',
onPressed: _isDownloadingEcard
? null
: () => getEcardDownload(empPrimaryId),
),
const SizedBox(width: 16),
],
_buildPolicyActionButton(
context: context,
label: 'Initiate a Claim',
svgKey: 'fileaclaims',
onPressed: () => context.push('/claims'),
),
],
);
}
return Column(
children: [
_buildPolicyActionButton(
context: context,
label: 'Initiate a Claim',
svgKey: 'fileaclaims',
onPressed: () => context.push('/claims'),
),
if (ECardHide) ...[
const SizedBox(height: 12),
_buildPolicyActionButton(
context: context,
label: 'E-Card',
svgKey: 'ECard',
onPressed: _isDownloadingEcard
? null
: () => getEcardDownload(empPrimaryId),
),
],
],
);
}
}