enrollment-app/lib/presentation/hrDashboard.dart
2026-03-26 09:45:12 +05:30

558 lines
18 KiB
Dart
Executable File

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; // Standard for Flutter 3.12+
import 'package:universal_html/html.dart' as html;
import '../config/environment.dart';
import '../customAppBar/base_layout.dart';
import '../service/secure_pop_scope.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 = [];
final Set<String> _registeredViewTypes = <String>{};
bool isDashboardLoading = false;
bool hasDashboardError = false;
bool _metabaseLoaded = false;
List<int> postModules = [];
String _dashboardViewType = '';
final tokenService = TokenStorageService();
final FocusNode _policyFocusNode = FocusNode();
bool isTpaDashboardEnabled = false;
bool isTpaSelected = false;
// Variables for API data
dynamic empClientBranchId;
dynamic empHrId;
dynamic empClientId;
String? _postPreToken = '';
int stausVal = 1;
bool _isPolicyDropdownOpen = false;
html.IFrameElement? _currentIframe;
@override
void initState() {
super.initState();
apiService = ApiService(context);
_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))) {
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(
branchId, clientId, hrId, token) async {
setState(() => isLoading = true);
try {
if (branchId == null || clientId == null) return;
final response = await apiService.getActiveCashDepositDetailsToApi(
clientId, branchId, hrId, token, stausVal);
if (response['status'] == 'success') {
final list = List<Map<String, dynamic>>.from(response['data']);
final List<Map<String, dynamic>> filteredList = list.where((item) {
final int policyTypeId =
int.tryParse(item['policy_type_id'].toString()) ?? 0;
return policyTypeId == 2 ||
policyTypeId == 3 ||
policyTypeId == 4 ||
policyTypeId == 5;
}).toList();
setState(() => activePoliciesList = filteredList);
if (list.isNotEmpty) {
selectedPolicyId = list.first['client_policy_id'].toString();
await _loadDashboardByPolicy(selectedPolicyId!);
} else {
setState(() => hasDashboardError = true);
}
}
} catch (e) {
debugPrint('Exception occurred: $e');
} finally {
setState(() => isLoading = false);
}
}
Future<void> _loadDashboardByPolicy(String clientPolicyId) async {
try {
setState(() {
isDashboardLoading = true;
_metabaseLoaded = false;
});
// ✅ REMOVE OLD IFRAME COMPLETELY
_currentIframe?.remove();
_currentIframe = null;
// ✅ CLEAR OLD VIEW TYPES
_registeredViewTypes.clear();
final response = await apiService.postHrDashboard({
"client_id": empClientId,
"client_policy_id": clientPolicyId,
}, _postPreToken);
if (response['status'] == 'success') {
// ✅ ADD THIS LINE
isTpaDashboardEnabled = response['is_tpa_dashboard_enable'] == true;
setState(() {
isTpaSelected = false;
});
_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);
}
}
Future<void> _loadTpaDashboard(String clientPolicyId) async {
try {
setState(() {
isDashboardLoading = true;
_metabaseLoaded = false;
});
// ✅ REMOVE OLD IFRAME
_currentIframe?.remove();
_currentIframe = null;
_registeredViewTypes.clear();
final response = await apiService.postHrTpaDashboard({
"client_id": empClientId,
"client_policy_id": clientPolicyId,
}, _postPreToken);
if (response['status'] == 'success') {
setState(() {
isTpaSelected = true;
});
_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, 'TPA Dashboard loading failed');
} finally {
setState(() => isDashboardLoading = false);
}
}
void _registerMetabaseIframe({
required String token,
required String url,
required String clientPolicyId,
}) {
// 🔥 ALWAYS CREATE UNIQUE VIEW TYPE
final viewType =
'metabase-dashboard-${clientPolicyId}-${DateTime.now().millisecondsSinceEpoch}';
_dashboardViewType = viewType;
final embedUrl = "$url/embed/dashboard/$token"
"#theme=light&bordered=true&titled=true"
"&v=${DateTime.now().millisecondsSinceEpoch}"; // 🔥 cache buster
final iframe = html.IFrameElement()
..src = embedUrl
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..allowFullscreen = true;
_currentIframe = iframe;
ui.platformViewRegistry.registerViewFactory(
viewType,
(int viewId) => iframe,
);
_registeredViewTypes.add(viewType);
}
@override
Widget build(BuildContext context) {
return BaseLayout(child: _buildContent(context));
}
Widget _buildContent(BuildContext context) {
return SecurePopScope(
child: Scaffold(
// ✅ REMOVED 'Expanded' from directly inside body.
body: Container(
width: double.infinity,
height: double.infinity,
child: isDashboardLoading
? _buildLoader()
: _metabaseLoaded
? _buildDashboardView()
: _buildEmptyState(),
),
),
);
}
Widget _buildDashboardView() {
if (postModules.isNotEmpty && activePoliciesList.isEmpty) {
return const Center(child: Text('No active policy found.'));
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Policy Selector
Material(
elevation: 2,
borderRadius: BorderRadius.circular(8),
child: _buildPolicySelector(),
),
const SizedBox(height: 50),
// Dashboard Area
Expanded(
child: IgnorePointer(
ignoring: _isPolicyDropdownOpen, // 🔥 KEY FIX
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade200),
),
child: HtmlElementView(
key: ValueKey(_dashboardViewType),
viewType: _dashboardViewType,
),
),
),
),
],
),
);
}
// Widget _buildPolicySelector() {
// return Container(
// padding: const EdgeInsets.all(16),
// color: Colors.white,
// child: Row(
// children: [
// const Text(
// 'Select Policy',
// style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13),
// ),
// const SizedBox(width: 12),
// SizedBox(
// width: 420,
// height: 40,
// child: SearchAnchor(
// builder: (BuildContext context, SearchController controller) {
// // --- Logic to find the current display text manually ---
// String displayText = "Select Policy";
// if (selectedPolicyId != null) {
// try {
// final currentPolicy = activePoliciesList.firstWhere(
// (p) => p['client_policy_id'].toString() == selectedPolicyId,
// );
// displayText = "${currentPolicy['type']} - ${currentPolicy['policy_no']}";
// } catch (e) {
// displayText = "Select Policy";
// }
// }
//
// return InkWell(
// onTap: () => controller.openView(),
// child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 12),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.grey.shade300),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// displayText,
// style: const TextStyle(fontSize: 12),
// ),
// const Icon(Icons.arrow_drop_down, color: Colors.white),
// ],
// ),
// ),
// );
// },
// suggestionsBuilder: (BuildContext context, SearchController controller) {
// final String input = controller.value.text.toLowerCase();
//
// return activePoliciesList
// .where((policy) =>
// policy['type'].toString().toLowerCase().contains(input) ||
// policy['policy_no'].toString().toLowerCase().contains(input))
// .map((policy) {
// final String displayLabel = "${policy['type']} - ${policy['policy_no']}";
//
// return ListTile(
// title: Text(displayLabel, style: const TextStyle(fontSize: 13)),
// onTap: () {
// setState(() {
// selectedPolicyId = policy['client_policy_id'].toString();
// controller.closeView(displayLabel);
// });
// _loadDashboardByPolicy(selectedPolicyId!);
// },
// );
// }).toList();
// },
// ),
// ),
// ],
// ),
// );
// }
// Widget _buildPolicySelector() {
// return Container(
// padding: const EdgeInsets.all(16),
// child: Row(
// children: [
// const Text('Select Policy', style: TextStyle(fontWeight: FontWeight.w600)),
// const SizedBox(width: 12),
// SizedBox(
// width: 420,
// child: DropdownButtonFormField<String>(
// 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(),
// onChanged: isDashboardLoading ? null : (value) {
// if (value == null || value == selectedPolicyId) return;
// setState(() => selectedPolicyId = value);
// _loadDashboardByPolicy(value);
// },
// decoration: InputDecoration(
// contentPadding: const EdgeInsets.symmetric(horizontal: 10),
// border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
// ),
// ),
// ),
// ],
// ),
// );
// }
Widget _buildPolicySelector() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Row(
children: [
if (!isTpaSelected) ...[
const Text(
'Select Policy',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13),
),
const SizedBox(width: 12),
SizedBox(
width: 420,
height: 40,
child: SearchAnchor(
viewBackgroundColor: Colors.white,
viewConstraints: const BoxConstraints(maxHeight: 220),
builder: (BuildContext context, SearchController controller) {
String displayText = "Select Policy";
if (selectedPolicyId != null) {
final policy = activePoliciesList.firstWhere(
(p) =>
p['client_policy_id'].toString() == selectedPolicyId,
orElse: () => {},
);
if (policy.isNotEmpty) {
displayText =
"${policy['type']} - ${policy['policy_no']}";
}
}
return InkWell(
onTap: () {
setState(() => _isPolicyDropdownOpen = true); // 🔥 OPEN
controller.openView();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
displayText,
style: const TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.arrow_drop_down, color: Colors.grey),
],
),
),
);
},
suggestionsBuilder:
(BuildContext context, SearchController controller) {
final input = controller.text.toLowerCase();
return activePoliciesList
.where((policy) =>
policy['type']
.toString()
.toLowerCase()
.contains(input) ||
policy['policy_no']
.toString()
.toLowerCase()
.contains(input))
.map((policy) {
final label = "${policy['type']} - ${policy['policy_no']}";
return ListTile(
dense: true,
title: Text(label, style: const TextStyle(fontSize: 13)),
onTap: () {
setState(() {
selectedPolicyId =
policy['client_policy_id'].toString();
_isPolicyDropdownOpen = false; // 🔥 CLOSE
});
controller.closeView(label);
_loadDashboardByPolicy(selectedPolicyId!);
},
);
}).toList();
},
),
),
],
const Spacer(),
if (isTpaDashboardEnabled)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isTpaSelected ? Colors.teal : Colors.grey.shade300,
foregroundColor: isTpaSelected ? Colors.white : Colors.black,
),
onPressed: () {
if (selectedPolicyId == null) return;
if (isTpaSelected) {
// 🔄 Switch BACK to Normal Dashboard
_loadDashboardByPolicy(selectedPolicyId!);
} else {
// 🔄 Switch TO TPA Dashboard
_loadTpaDashboard(selectedPolicyId!);
}
},
child: Text(
isTpaSelected ? "Insights from Nhance" : "Insights from TPA ",
),
),
],
),
);
}
Widget _buildLoader() => Center(
child: Image.asset('assets/nhance-loader.gif', height: 60, width: 60),
);
Widget _buildEmptyState() => const Center(
child: Text('No dashboard data available',
style: TextStyle(color: Colors.grey)),
);
Future<bool> _showLogoutDialog() async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text("Confirm Logout"),
content: const Text("Do you want to logout?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text("Cancel")),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text("Logout")),
],
),
) ??
false;
}
}