enrollment-app/lib/presentation/hrDashboard.dart

474 lines
16 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';
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();
// Variables for API data
dynamic empClientBranchId;
dynamic empHrId;
dynamic empClientId;
String? _postPreToken = '';
int stausVal = 1;
bool _isPolicyDropdownOpen = false;
@override
void initState() {
super.initState();
apiService = ApiService(context);
html.window.onPopState.listen((event) async {
final shouldLogout = await _showLogoutDialog();
if (shouldLogout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
} else {
// Push state back to prevent browser navigation
html.window.history.pushState(null, '', html.window.location.href);
}
});
_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;
});
final response = await apiService.postHrDashboard({
"client_id": empClientId,
"client_policy_id": clientPolicyId,
}, _postPreToken);
if (response['status'] == 'success') {
_registerMetabaseIframe(
token: response['data']['metabaseToken'],
url: response['data']['metabaseUrl'],
clientPolicyId: clientPolicyId,
);
setState(() => _metabaseLoaded = true);
} else {
ToastHelper.showErrorToast(context, response['message']);
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Dashboard loading failed');
} finally {
setState(() => isDashboardLoading = false);
}
}
void _registerMetabaseIframe({
required String token,
required String url,
required String clientPolicyId,
}) {
final viewType = 'metabase-dashboard-$clientPolicyId';
_dashboardViewType = viewType;
if (_registeredViewTypes.contains(viewType)) return;
final embedUrl = "$url/embed/dashboard/$token#theme=light&bordered=true&titled=true";
ui.platformViewRegistry.registerViewFactory(
viewType,
(int viewId) => html.IFrameElement()
..src = embedUrl
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..allowFullscreen = true,
);
_registeredViewTypes.add(viewType);
}
@override
Widget build(BuildContext context) {
return BaseLayout(child: _buildContent(context));
}
Widget _buildContent(BuildContext context) {
return PopScope(
canPop: false,
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 dashboard data available for your account'));
}
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: [
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();
},
),
),
],
),
);
}
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;
}
}