import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../config/environment.dart'; import '../service/token_storage_service.dart'; class NhanceTopBar extends StatefulWidget implements PreferredSizeWidget { const NhanceTopBar({super.key}); @override Size get preferredSize => const Size.fromHeight(64); @override State createState() => _NhanceTopBarState(); } class _NhanceTopBarState extends State { final tokenStorage = TokenStorageService(); List> branches = []; Map? selectedBranch; /// client_id → logo URL final Map _logoByClientId = {}; @override void initState() { super.initState(); _loadBranches(); } void _loadBranches() { branches = tokenStorage.getCombinedBranches(); selectedBranch = tokenStorage.getSelectedBranch(); setState(() {}); _loadBranchLogos(); } String _clientKey(Map branch) { return (branch['client_id'] ?? branch['id'] ?? '').toString(); } String? _logoFromBranch(Map branch) { final candidates = [ branch['client_logo'], branch['logo'], branch['clientLogo'], ]; for (final value in candidates) { final url = value?.toString().trim() ?? ''; if (url.isNotEmpty) return url; } return null; } Map? _decodeBranchToken(String token) { try { final parts = token.split('.'); if (parts.length != 3) return null; final payload = base64Url.normalize(parts[1]); final decoded = utf8.decode(base64Url.decode(payload)); final parsed = json.decode(decoded); if (parsed is Map) return parsed; if (parsed is Map) return Map.from(parsed); return null; } catch (_) { return null; } } /// Read post/pre client + branch ids from selected branch JWT payload. Map? _clientDetailsParamsFromBranch( Map branch, ) { final token = branch['token']?.toString() ?? tokenStorage.getCurrentToken() ?? ''; if (token.isEmpty) return null; final decoded = _decodeBranchToken(token); if (decoded == null) return null; final postClientId = decoded['post_client_id']?.toString().trim() ?? ''; final postBranchId = decoded['post_branch_id']?.toString().trim() ?? ''; final preClientId = decoded['pre_client_id']?.toString().trim() ?? ''; final preBranchId = decoded['pre_branch_id']?.toString().trim() ?? ''; if (postClientId.isEmpty && postBranchId.isEmpty && preClientId.isEmpty && preBranchId.isEmpty) { return null; } return { 'post_client_id': postClientId, 'post_branch_id': postBranchId, 'pre_client_id': preClientId, 'pre_branch_id': preBranchId, }; } Future _loadBranchLogos() async { final uniqueBranches = >{}; for (final branch in branches) { final key = _clientKey(branch); if (key.isEmpty) continue; uniqueBranches.putIfAbsent(key, () => branch); } for (final entry in uniqueBranches.entries) { final key = entry.key; if (_logoByClientId.containsKey(key)) continue; final fromBranch = _logoFromBranch(entry.value); if (fromBranch != null) { if (!mounted) return; setState(() => _logoByClientId[key] = fromBranch); continue; } final logoUrl = await _fetchClientLogoUrl(entry.value); if (!mounted) return; if (logoUrl != null && logoUrl.isNotEmpty) { setState(() => _logoByClientId[key] = logoUrl); } } } Future _fetchClientLogoUrl(Map branch) async { try { final token = branch['token']?.toString() ?? tokenStorage.getCurrentToken() ?? ''; if (token.isEmpty) return null; final params = _clientDetailsParamsFromBranch(branch); if (params == null) return null; final url = Uri.parse('${Environment.apiUrl}getClientDetails').replace( queryParameters: params, ); final response = await http.get( url, headers: { 'Authorization': 'Bearer $token', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode != 200) return null; final data = jsonDecode(response.body); if (data is! Map || data['status'] != 'success') return null; final logo = data['data']?['client']?['client_logo']?.toString().trim(); if (logo == null || logo.isEmpty) return null; return logo; } catch (_) { return null; } } Future _getBranchSwitchRoute() async { final postRaw = await tokenStorage.readValue('empAllowed_modules'); final postModules = postRaw != null && postRaw.isNotEmpty ? List.from(jsonDecode(postRaw)) : []; if (postModules.isNotEmpty && postModules.contains(5)) { return 'claimsOverviewDashboard'; } return 'policies'; } Future _onBranchSelected(Map branch) async { final tokenStorage = TokenStorageService(); await tokenStorage.resetSessionAndSwitchBranch(branch); if (!mounted) return; _loadBranches(); final route = await _getBranchSwitchRoute(); if (!mounted) return; Navigator.pushReplacementNamed(context, route); } @override Widget build(BuildContext context) { final selectedLogo = selectedBranch == null ? null : _logoByClientId[_clientKey(selectedBranch!)]; return AppBar( automaticallyImplyLeading: false, backgroundColor: const Color(0xFFBFEFEF), elevation: 0, title: Row( children: [ Image.asset('assets/nhance_client_logo.png', height: 32), const Spacer(), if (selectedBranch != null) _BranchPopup( clientName: selectedBranch!['client_name']?.toString() ?? '', branchName: selectedBranch!['branch_name']?.toString() ?? '', selectedLogoUrl: selectedLogo, branches: branches, logoByClientId: _logoByClientId, clientKeyBuilder: _clientKey, onSelected: _onBranchSelected, ), ], ), ); } } class _BranchPopup extends StatelessWidget { final String clientName; final String branchName; final String? selectedLogoUrl; final List> branches; final Map logoByClientId; final String Function(Map) clientKeyBuilder; final Function(Map) onSelected; const _BranchPopup({ required this.clientName, required this.branchName, required this.selectedLogoUrl, required this.branches, required this.logoByClientId, required this.clientKeyBuilder, required this.onSelected, }); @override Widget build(BuildContext context) { return PopupMenuButton>( tooltip: '', offset: const Offset(0, 48), onSelected: onSelected, itemBuilder: (context) { return branches.map((branch) { final logoUrl = logoByClientId[clientKeyBuilder(branch)]; return PopupMenuItem>( value: branch, child: Row( children: [ _BranchLogo(logoUrl: logoUrl, size: 24), const SizedBox(width: 8), Expanded( child: Text( branch['client_name']?.toString() ?? '', style: const TextStyle(fontSize: 13), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 10), const Icon(Icons.location_on, size: 14, color: Colors.grey), const SizedBox(width: 4), Flexible( child: Text( branch['branch_name']?.toString() ?? '', style: const TextStyle( fontSize: 12, color: Colors.grey, ), overflow: TextOverflow.ellipsis, ), ), ], ), ); }).toList(); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ _BranchLogo(logoUrl: selectedLogoUrl, size: 32), const SizedBox(width: 8), Container( height: 40, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Text( clientName, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 8), Row( children: [ const Icon( Icons.location_on, size: 14, color: Colors.grey, ), const SizedBox(width: 4), Text( branchName, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), const SizedBox(width: 6), const Icon( Icons.keyboard_arrow_down, size: 20, color: Colors.orange, ), ], ), ), ], ), ); } } class _BranchLogo extends StatelessWidget { final String? logoUrl; final double size; const _BranchLogo({ required this.logoUrl, required this.size, }); @override Widget build(BuildContext context) { final url = logoUrl?.trim() ?? ''; if (url.isEmpty) { return _placeholder(); } return ClipRRect( borderRadius: BorderRadius.circular(4), child: Image.network( url, width: size, height: size, fit: BoxFit.contain, errorBuilder: (_, __, ___) => _placeholder(), loadingBuilder: (context, child, progress) { if (progress == null) return child; return SizedBox( width: size, height: size, child: const Padding( padding: EdgeInsets.all(4), child: CircularProgressIndicator(strokeWidth: 1.5), ), ); }, ), ); } Widget _placeholder() { return Container( width: size, height: size, decoration: BoxDecoration( color: const Color(0xFFE8F5F5), borderRadius: BorderRadius.circular(4), border: Border.all(color: const Color(0xFFBFEFEF)), ), child: Icon( Icons.business, size: size * 0.6, color: const Color(0xFF00999E), ), ); } }