enrollment-app/lib/customAppBar/top_app_bar.dart
2026-08-06 15:03:54 +05:30

401 lines
11 KiB
Dart

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<NhanceTopBar> createState() => _NhanceTopBarState();
}
class _NhanceTopBarState extends State<NhanceTopBar> {
final tokenStorage = TokenStorageService();
List<Map<String, dynamic>> branches = [];
Map<String, dynamic>? selectedBranch;
/// client_id → logo URL
final Map<String, String> _logoByClientId = {};
@override
void initState() {
super.initState();
_loadBranches();
}
void _loadBranches() {
branches = tokenStorage.getCombinedBranches();
selectedBranch = tokenStorage.getSelectedBranch();
setState(() {});
_loadBranchLogos();
}
String _clientKey(Map<String, dynamic> branch) {
return (branch['client_id'] ?? branch['id'] ?? '').toString();
}
String? _logoFromBranch(Map<String, dynamic> 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<String, dynamic>? _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<String, dynamic>) return parsed;
if (parsed is Map) return Map<String, dynamic>.from(parsed);
return null;
} catch (_) {
return null;
}
}
/// Read post/pre client + branch ids from selected branch JWT payload.
Map<String, String>? _clientDetailsParamsFromBranch(
Map<String, dynamic> 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<void> _loadBranchLogos() async {
final uniqueBranches = <String, Map<String, dynamic>>{};
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<String?> _fetchClientLogoUrl(Map<String, dynamic> 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<String> _getBranchSwitchRoute() async {
final postRaw = await tokenStorage.readValue('empAllowed_modules');
final postModules = postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: <int>[];
if (postModules.isNotEmpty && postModules.contains(5)) {
return 'claimsOverviewDashboard';
}
return 'policies';
}
Future<void> _onBranchSelected(Map<String, dynamic> 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<Map<String, dynamic>> branches;
final Map<String, String> logoByClientId;
final String Function(Map<String, dynamic>) clientKeyBuilder;
final Function(Map<String, dynamic>) 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<Map<String, dynamic>>(
tooltip: '',
offset: const Offset(0, 48),
onSelected: onSelected,
itemBuilder: (context) {
return branches.map((branch) {
final logoUrl = logoByClientId[clientKeyBuilder(branch)];
return PopupMenuItem<Map<String, dynamic>>(
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),
),
);
}
}