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 createState() => _hrDashboardState(); } class _hrDashboardState extends State with SingleTickerProviderStateMixin { late ApiService apiService; bool isLoading = false; String? selectedPolicyId; List> activePoliciesList = []; final Set _registeredViewTypes = {}; bool isDashboardLoading = false; bool hasDashboardError = false; bool _metabaseLoaded = false; List 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 _loadToken() async { _postPreToken = await tokenService.getCurrentToken(); final postRaw = await tokenService.readValue('empAllowed_modules'); postModules = postRaw != null && postRaw.isNotEmpty ? List.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 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>.from(response['data']); final List> 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 _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 _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( // value: selectedPolicyId, // isExpanded: true, // items: activePoliciesList.map((policy) { // return DropdownMenuItem( // 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 _showLogoutDialog() async { return await showDialog( 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; } }