558 lines
18 KiB
Dart
Executable File
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;
|
|
}
|
|
}
|