new design bug issue fix

This commit is contained in:
Surendiran 2026-02-13 09:16:57 +05:30
parent b68d89670c
commit 1fb7c324a0
20 changed files with 2379 additions and 2031 deletions

View File

@ -17,47 +17,52 @@ class BranchCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
decoration: BoxDecoration(
color: isSelected ? Color(0xFF00999E) : Color(0xFFF0F9F9),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Color(0xFF00999E),
width: 1.5,
return Tooltip(
message: clientName, // 🖱 hover shows full name
waitDuration: const Duration(milliseconds: 400),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF00999E) : const Color(0xFFF0F9F9),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFF00999E),
width: 1.5,
),
),
),
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
clientName,
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : Color(0xFF00999E),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
clientName,
maxLines: 1, // SINGLE LINE
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w500,
color:
isSelected ? Colors.white : const Color(0xFF00999E),
),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
branchName,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: isSelected ? Color(0xFFFDFDFD) : Color(0xFF000000),
const SizedBox(height: 4),
Text(
branchName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 11,
color:
isSelected ? Colors.white70 : Colors.black87,
),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
),
);
}
}
}

View File

@ -1,14 +1,10 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import '../customAppBar/base_layout.dart';
import '../customAppBar/customAppBar.dart';
import '../customAppBar/customFooter.dart';
import '../customAppBar/base_layout.dart';
import '../customAppBar/toastHelper.dart';
import '../service/api_service.dart';
import '../service/session/web_session.dart';
import '../service/token_storage_service.dart';
import 'branch_card_widget.dart';
import 'dart:html' as html;
class BranchSelectionPage extends StatefulWidget {
const BranchSelectionPage({Key? key}) : super(key: key);
@ -19,14 +15,30 @@ class BranchSelectionPage extends StatefulWidget {
class _BranchSelectionPageState extends State<BranchSelectionPage> {
List<Map<String, dynamic>> branches = [];
late ApiService apiService;
int? selectedIndex;
final tokenStorage = TokenStorageService();
late ApiService apiService;
@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);
}
});
_loadBranches();
}
@ -37,25 +49,18 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
}
void _selectBranch(int index) {
setState(() {
selectedIndex = index;
});
setState(() => selectedIndex = index);
}
Future<void> _handleNext() async {
if (selectedIndex == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select a branch'),
backgroundColor: Colors.orange,
),
const SnackBar(content: Text('Please select a branch')),
);
return;
}
final selectedBranch = branches[selectedIndex!];
// Save selected branch & decode token securely
await tokenStorage.saveSelectedBranch(selectedBranch);
final decodedToken = tokenStorage.getDecodedToken();
@ -97,169 +102,121 @@ class _BranchSelectionPageState extends State<BranchSelectionPage> {
false;
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
canPop: false,
child: _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
// Calculate dynamic horizontal padding (10% of screen width)
final horizontalPadding = screenWidth * 0.1;
// Calculate dynamic grid height
double gridHeight = screenHeight * 0.45;
if (gridHeight < 300) gridHeight = 300;
if (gridHeight > 600) gridHeight = 600;
// Determine crossAxisCount with multiple breakpoints
int crossAxisCount;
double childAspectRatio;
if (screenWidth < 600) {
// Mobile phones
crossAxisCount = 2;
childAspectRatio = 2.5;
} else if (screenWidth < 900) {
// Small tablets
crossAxisCount = 2;
childAspectRatio = 2.8;
} else if (screenWidth < 1200) {
// Large tablets
crossAxisCount = 3;
childAspectRatio = 3;
} else {
// Desktop
crossAxisCount = 3;
childAspectRatio = 4;
}
Widget _buildContent(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF5F7F7),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: horizontalPadding, // Inner horizontal padding
vertical: 20, // Inner vertical padding
), // Outer padding
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
backgroundColor: const Color(0xFFF5F7F7),
body: LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
/// 🔹 RESPONSIVE BREAKPOINTS
int crossAxisCount;
if (width < 600) {
crossAxisCount = 1;
} else if (width < 900) {
crossAxisCount = 2;
} else {
crossAxisCount = 3;
}
return SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: width * 0.08,
vertical: 30,
),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 12,
offset: const Offset(0, 4),
),
padding: const EdgeInsets.symmetric(
horizontal: 24.0, // Inner horizontal padding
vertical: 28.0, // Inner vertical padding
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Select Client',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Select Client',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
SizedBox(height: 25),
Container(
height: gridHeight, // Shows approximately 3 rows
child: branches.isEmpty
? Center(
child: Text(
'No branches available',
style: TextStyle(
fontSize: 16, color: Colors.grey),
const SizedBox(height: 24),
/// 🔹 GRID
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: branches.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisExtent: 90, // 🔥 FIXED HEIGHT
mainAxisSpacing: 15,
crossAxisSpacing: 15,
),
itemBuilder: (context, index) {
final branch = branches[index];
return BranchCard(
clientName: branch['client_name'] ??
'Unknown Client kjbgjsk jsdhbjbf sdjfjbds fdjsgjdbs gdsjbgjds',
branchName:
branch['branch_name'] ?? 'Unknown Branch',
isSelected: selectedIndex == index,
onTap: () => _selectBranch(index),
);
},
),
const SizedBox(height: 30),
/// 🔹 NEXT BUTTON
Center(
child: SizedBox(
width: 140,
height: 46,
child: ElevatedButton(
onPressed: _handleNext,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF6B35),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
)
: GridView.builder(
// Enable scrolling if content exceeds height
physics: ClampingScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
childAspectRatio: childAspectRatio,
crossAxisSpacing: 15,
mainAxisSpacing: 15,
),
itemCount: branches.length,
itemBuilder: (context, index) {
final branch = branches[index];
return BranchCard(
clientName: branch['client_name'] ??
'Unknown Client',
branchName: branch['branch_name'] ??
'Unknown Branch',
isSelected: selectedIndex == index,
onTap: () => _selectBranch(index),
);
},
elevation: 0,
),
),
SizedBox(height: 24),
Center(
child: SizedBox(
width: 120,
height: 48,
child: ElevatedButton(
onPressed: _handleNext,
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFFF6B35),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 0,
),
child: Text(
'Next',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
child: const Text(
'Next',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
],
),
),
),
],
),
),
),
],
);
},
),
);
}
}
}

View File

@ -44,4 +44,31 @@ class Environment {
return "/";
}
}
}
static String getPageTitle(String? routeName) {
String base = "Nhance HR";
if (isProd) base = "Nhance HR"; // You can differentiate names if needed
switch (routeName) {
case 'hrDashboard':
return "$base - Insights";
case 'policies':
return "$base - Policies";
case 'CdPoliciesList':
return "$base - CD Details";
case 'ClaimsPolicies':
return "$base - Claims";
case 'hrLogin':
return "$base - Login";
case 'cdTransactionDetails':
return "$base - Transaction Details";
case 'hrPolicyDetails':
return "$base - Member Details";
case 'preFileUpload':
return "$base - Member Upload";
case 'postFileUpload':
return "$base - Member Upload";
default:
return base;
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'top_app_bar.dart';
import 'side_bar.dart';
import '../config/environment.dart';
class BaseLayout extends StatelessWidget {
final Widget child;
@ -9,7 +10,14 @@ class BaseLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
// 1. Capture the current route name from the navigation context
final String? routeName = ModalRoute.of(context)?.settings.name;
// 2. Use the Title widget to communicate with the browser's tab
return Title(
title: Environment.getPageTitle(routeName),
color: const Color(0xFF00999E), // Required, usually matches your theme
child: Scaffold(
appBar: const NhanceTopBar(),
body: Row(
children: [
@ -23,7 +31,7 @@ class BaseLayout extends StatelessWidget {
),
],
),
);
) );
}
}

View File

@ -23,6 +23,8 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
// dynamic postModules = [];
List<Map<String, dynamic>> sideMenuItems = [];
List<int> postModules = [];
List<int> enrollmentModules = [];
@override
@ -46,12 +48,12 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
print('postRaw $postRaw');
// Decode safely
final List<int> enrollmentModules =
enrollmentModules =
enrollmentRaw != null && enrollmentRaw.isNotEmpty
? List<int>.from(jsonDecode(enrollmentRaw))
: [];
final List<int> postModules =
postModules =
postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: [];
@ -107,16 +109,20 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
activeRoute = ModalRoute.of(context)?.settings.name;
// Safely capture the current route name
final newRoute = ModalRoute.of(context)?.settings.name;
if (activeRoute != newRoute) {
setState(() {
activeRoute = newRoute;
});
// Re-verify the menu items if the route changes
_buildSideMenu();
}
}
void _navigate(String routeName) {
if (activeRoute == routeName) return;
setState(() {
activeRoute = routeName;
});
// Navigation should happen first, didChangeDependencies will handle the state
Navigator.pushReplacementNamed(context, routeName);
}
@ -214,7 +220,7 @@ class _NhanceSideBarState extends State<NhanceSideBar> {
);
}).toList(),
if(activeRoute == 'CdPoliciesList' || activeRoute == 'ClaimsPolicies' || activeRoute == 'policies' || activeRoute == 'hrDashboard')
if(postModules.isNotEmpty)
_SideItem(
// icon: Icons.dashboard,
icon: SvgPicture.string(

View File

@ -116,7 +116,12 @@ class ToastHelper {
type: ToastificationType.error,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(message),
title: Text(
message,
maxLines: 3, // allow wrapping
overflow: TextOverflow.visible,
softWrap: true,
),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),

View File

@ -160,12 +160,42 @@ class _MyEmailVerifyState extends State<MyEmailVerify> {
);
// Navigate to branch selection
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BranchSelectionPage(),
),
);
// 🔥 Get all valid branches
final branches = tokenStorage.getCombinedBranches();
if (branches.length == 1) {
// ONLY ONE BRANCH AUTO SELECT
final singleBranch = branches.first;
// Save selected branch
await tokenStorage.saveSelectedBranch(singleBranch);
final decodedToken = tokenStorage.getDecodedToken();
final token = tokenStorage.getCurrentToken();
if (decodedToken == null || token == null) {
ToastHelper.showErrorToast(context, 'Invalid token data');
return;
}
// Save decoded session values
await tokenStorage.saveDecodedSessionData(decodedToken, token);
if (!mounted) return;
// 🚀 DIRECTLY GO TO POLICIES
Navigator.pushReplacementNamed(context, 'policies');
} else {
// 🧭 MULTIPLE BRANCHES SHOW SELECTION PAGE
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const BranchSelectionPage(),
),
);
}
// print('data: $data');
// _token = data['data'];
// String status = data['status'];

View File

@ -168,7 +168,9 @@ class _MyPhoneState extends State<MyHrLogin> {
setState(() {
_isLoading = false;
});
ToastHelper.showErrorToast(context, 'Something went wrong');
Map<String, dynamic> data = json.decode(response.body);
final message = data['message'];
ToastHelper.showErrorToast(context, message);
throw Exception('Failed to verify mobile number');
}
}

View File

@ -63,6 +63,8 @@ Future<void> startApp() async {
// await dotenv.load(fileName: Environment.fileName);
runApp(MaterialApp(
title: 'Nhance HR',
onGenerateTitle: (context) => "Nhance HR",
initialRoute: 'hrLogin',
debugShowCheckedModeBanner: false,
theme: ThemeData(

View File

@ -84,6 +84,7 @@ class _RaiseClaimDialogState extends State<RaiseClaimDialog> {
int? selectedClientPolicyId;
Map<String, dynamic>? selectedMemberObject;
TextEditingController searchController = TextEditingController();
// Declare subjectController and bodyController as instance variables
late TextEditingController subjectController;
@ -1091,23 +1092,50 @@ class _RaiseClaimDialogState extends State<RaiseClaimDialog> {
String displayField,
int? selectedValue,
) {
final TextEditingController searchController = TextEditingController();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
fieldLabel(label),
formBox(
Text(
label,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
const SizedBox(height: 4),
Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Color(0xFFF1F1F1),
borderRadius: BorderRadius.circular(8),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.08),
// blurRadius: 8,
// offset: const Offset(0, 2),
// ),
// ],
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<int>(
isExpanded: true,
value: selectedValue,
hint: const Text('Select'),
hint: const Text(
'Select',
style: TextStyle(fontSize: 12),
),
iconStyleData: const IconStyleData(
icon: Icon(Icons.keyboard_arrow_down),
),
// 🔹 SEARCH CONFIG
dropdownStyleData: DropdownStyleData(
maxHeight: 260,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
// 🔍 SEARCH SUPPORT
dropdownSearchData: DropdownSearchData(
searchController: searchController,
searchInnerWidgetHeight: 50,
@ -1115,24 +1143,20 @@ class _RaiseClaimDialogState extends State<RaiseClaimDialog> {
padding: const EdgeInsets.all(8),
child: TextField(
controller: searchController,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
hintText: 'Search...',
hintStyle: const TextStyle(fontSize: 12),
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
isDense: true,
),
),
),
searchMatchFn: (item, searchValue) {
return item.value
.toString()
.toLowerCase()
.contains(searchValue.toLowerCase()) ||
item.child
.toString()
.toLowerCase()
.contains(searchValue.toLowerCase());
final text = item.child.toString().toLowerCase();
return text.contains(searchValue.toLowerCase());
},
),

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:nhancepolicy/presentation/policies.dart';
import 'package:universal_html/html.dart' as html;
import 'cdTransactionDetails.dart';
@ -57,10 +58,26 @@ class _CdPoliciesListState extends State<CdPoliciesList> {
return filteredData.sublist(startIndex, endIndex);
}
@override
void initState() {
super.initState();
apiService = ApiService(context); // Initialize ApiService here
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);
}
});
checkIds();
}
@ -233,19 +250,7 @@ class _CdPoliciesListState extends State<CdPoliciesList> {
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
canPop: false,
child: _buildContent(context),
),
);
@ -267,16 +272,16 @@ class _CdPoliciesListState extends State<CdPoliciesList> {
/// 🔙 Back + Title (LEFT)
Row(
children: [
IconButton(
onPressed: () => {},
icon: const Icon(
Icons.arrow_back_ios,
size: 18,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
// IconButton(
// onPressed: () => {},
// icon: const Icon(
// Icons.arrow_back_ios,
// size: 18,
// color: Colors.black,
// ),
// padding: EdgeInsets.zero,
// constraints: const BoxConstraints(),
// ),
const SizedBox(width: 6),
Text(
'CD',
@ -362,20 +367,42 @@ class _CdPoliciesListState extends State<CdPoliciesList> {
);
}
Widget _buildCDGrid() {
if (filteredData.isEmpty) {
return const Center(
child: Text('No CD Account Mapped'),
);
}
ResponsiveGridConfig _getGridConfig(
BuildContext context,
bool isEnrollment,
) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return ResponsiveGridConfig(1, 3.8);
} else if (width < 900) {
return ResponsiveGridConfig(2, 3.8);
} else if (width < 1400) {
return ResponsiveGridConfig(3, 4.2);
} else {
return ResponsiveGridConfig(4, 3.8);
}
}
final config = _getGridConfig(context, true);
return GridView.builder(
itemCount: filteredData.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // desktop
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: config.crossAxisCount,
childAspectRatio: config.childAspectRatio,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 3.8, // 🔥 matches image
),
itemBuilder: (context, index) {
return InkWell(
@ -385,6 +412,7 @@ class _CdPoliciesListState extends State<CdPoliciesList> {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: 'cdTransactionDetails'),
builder: (_) => cdTransactionDetails(
insurerName: filteredData[index]['insurer_name'],
cdMasterAccountNo: filteredData[index]['cd_master_account_no'],
@ -428,13 +456,13 @@ class _CDPolicyCard extends StatelessWidget {
border: Border.all(color: const Color(0xFFA0D1D3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 7,
flex: 7,
child: Text(
data['insurer_name'] ?? '',
maxLines: 2,
@ -447,7 +475,7 @@ class _CDPolicyCard extends StatelessWidget {
),
),
Expanded(
flex: 5,
flex: 3,
child: Text(
"${balance.toStringAsFixed(0)}",
textAlign: TextAlign.right,
@ -466,14 +494,20 @@ class _CDPolicyCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
/// Account number
Text(
'CD Account Number: ${data['cd_master_account_no']}',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
Expanded(
flex: 8,
child: Text(
'CD No: ${data['cd_master_account_no']}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
),
),
),
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(

View File

@ -67,6 +67,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
dynamic total_consumed;
dynamic total_refund;
dynamic currect_balance;
dynamic insurer_short_name;
int inceptionType = 0;
TextEditingController searchController = TextEditingController();
late ApiService apiService;
@ -114,10 +115,11 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
List<Map<String, dynamic>>.from(response['data']['deposit_data']);
originalData = getCDTransData;
filteredData = List.from(originalData);
total_deposit = response['data']['total_deposit'];
total_consumed = response['data']['total_consumed'];
total_refund = response['data']['total_refund'];
currect_balance = response['data']['currect_balance'];
total_deposit = formatAmount(response['data']['total_deposit']);
total_consumed = formatAmount(response['data']['total_consumed']);
total_refund = formatAmount(response['data']['total_refund']);
currect_balance = formatAmount(response['data']['currect_balance']);
insurer_short_name = formatAmount(response['data']['insurer_short_name']);
print('filteredData');
print(filteredData);
});
@ -401,7 +403,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.AnchorElement(href: url)
..setAttribute("download", "CD_Policies.csv")
..setAttribute("download", "CD_Transaction_Policies.csv")
..click();
html.Url.revokeObjectUrl(url);
handleExportAction();
@ -441,7 +443,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
try {
DateTime parsedDate = DateTime.parse(dateString);
return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate);
return DateFormat('dd-MM-yyyy').format(parsedDate);
} catch (e) {
return '-';
}
@ -495,22 +497,22 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
false;
}
String formatAmount(dynamic value) {
if (value == null) return '-';
final num amount = num.tryParse(value.toString()) ?? 0;
return amount.round().toString();
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
if (didPop) return;
Navigator.pop(context); // 👈 go to previous page
},
child: _buildContent(context),
),
@ -519,7 +521,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
Widget _buildContent(BuildContext context) {
return isLoading ? Container(
color: Color(0x98FFFCE5), // Semi-transparent background
color: Colors.transparent, // Semi-transparent background
child: Center(
child: // Your GIF loader widget
Image.asset(
@ -538,6 +540,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
Row(
children: [
IconButton(
tooltip: 'Previous Page',
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.arrow_back_ios,
@ -549,7 +552,7 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
),
const SizedBox(width: 6),
Text(
'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})',
'Transaction Details - ${insurer_short_name} (${widget.cdMasterAccountNo})',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w500,
@ -627,30 +630,12 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
],
),
SizedBox(height: 16),
Container(
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(
10), // 👈 set your desired radius
),
// height: 400,
// padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: SingleChildScrollView(
child: _buildCDDataTable(context),
),
)
],
),
],
),
)
Expanded(
child: _buildCDDataTable(context),
),
],
));
)
);
}
@ -711,439 +696,348 @@ class _cdTransactionDetailsState extends State<cdTransactionDetails> {
Widget _buildCDDataTable(BuildContext context) {
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
return const Center(child: Text('No available data'));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header row
Container(
decoration: BoxDecoration(
color: Color(0xFFD7E9EB),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
'Date',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Record Date',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 4,
child: Text(
'Unit',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 3,
child: Text(
'Policy',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 3,
child: Text(
'Endorsement No',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Sub Type',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Credit',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Debit',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Balance',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 3,
child: Text(
'Description',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'User',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000),
fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
'Action',
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 12,
color: const Color(0xFF000000),
fontWeight: FontWeight.bold,
),
),
),
return CustomScrollView(
slivers: [
/// 🔒 FIXED HEADER
SliverPersistentHeader(
pinned: true,
delegate: _CDTableHeaderDelegate(),
),
],
/// 📄 TABLE ROWS
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final item = _paginatedData[index];
return _buildCDRow(item);
},
childCount: _paginatedData.length,
),
),
const SizedBox(height: 6),
// Table body rows
..._paginatedData.mapIndexed((index, item) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
decoration: BoxDecoration(
// color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white,
// color: Colors.white,
// borderRadius: BorderRadius.circular(6),
border: Border(
bottom: BorderSide(
color: Color(0xFFA9D9DE), // 👈 Bottom border color
width: 1, // 👈 Optional: thickness
),
),
),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
formatDateNextLine(item['created_at']),
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
item['record_date'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 4,
child: Text(
// 'The Kancheepuram District Consumers Operative Wholesale Stores Limited-5526',
item['unit'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 3,
child: Text(
// 'Motor - 3001/379707905/00/000',
item['policy_type'] != null &&
item['policy_type'].toString().trim().isNotEmpty &&
item['policy_no'] != null &&
item['policy_no'].toString().trim().isNotEmpty
// ? '${item['policy_no']}'
? '${item['policy_type']} - ${item['policy_no']}'
: '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 3,
child: Text(
item['endorsement_no'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
item['sub_type_text'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
item['transaction_type'] == 'Credit'
? '${item['amount']}'
: '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
item['transaction_type'] == 'Debit'
? '${item['amount']}'
: '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
"${item['balance'] ?? '-'}",
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 3,
child: Text(
item['description'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
item['username'] ?? '-',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Color(0xFF000000),
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_ActionIconButton(
icon: Icons.picture_as_pdf_outlined,
onTap: () {
getCdEndorsementDetails(item['id']);
},
subType: item['sub_type']
),
const SizedBox(width: 8),
_ActionIconButton(
icon: Icons.folder_open_outlined,
onTap: () {
if (item['split_up_url'] != null && item['split_up_url'].toString().trim().isNotEmpty){
_launchURL(item['split_up_url']);
} else{
ToastHelper.showWarningToast(context, 'File not generated');
}
},
subType: item['sub_type']
),
],
),
),
],
),
);
}).toList(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<int>(
value: _rowsPerPage,
items: [5, 10, 15, 20, 50].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(
' $value ',
style: GoogleFonts.poppins(fontSize: 15),
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_rowsPerPage = newValue!;
_currentPage = 1;
});
},
),
IconButton(
onPressed: _currentPage > 1
? () {
setState(() {
_currentPage--;
});
}
: null,
icon: Icon(Icons.chevron_left),
),
for (int i = 1;
i <= (filteredData.length / _rowsPerPage).ceil();
i++)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: _currentPage == i
? Color(0xFF00A6A6)
: Colors.grey[300],
foregroundColor:
_currentPage == i ? Colors.white : Colors.black,
minimumSize: Size(36, 36),
padding: EdgeInsets.zero,
),
onPressed: () {
setState(() {
_currentPage = i;
});
},
child: Text(i.toString()),
),
),
IconButton(
onPressed: _currentPage <
(filteredData.length / _rowsPerPage).ceil()
? () {
setState(() {
_currentPage++;
});
}
: null,
icon: Icon(Icons.chevron_right),
),
],
),
),
],
/// 📌 PAGINATION
SliverToBoxAdapter(
child: _buildPagination(context),
),
],
);
}
Widget _buildCDRow(Map<String, dynamic> item) {
final bool isAllowedSubType =
item['sub_type'] == '3' || item['sub_type'] == '4';
final bool hasSplitUpFile =
item['split_up_url'] != null &&
item['split_up_url'].toString().trim().isNotEmpty;
return Container(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFA9D9DE), width: 1),
),
),
child: Row(
children: [
_cell(formatDateNextLine(item['created_at']), 2),
SizedBox(width: 10),
_cell(formatDate(item['record_date']), 2),
SizedBox(width: 10),
_cell(item['unit'], 2),
SizedBox(width: 10),
_cell(
item['policy_type'] != null && item['policy_no'] != null
? '${item['policy_type']} - ${item['policy_no']}'
: '-',
3,
),
SizedBox(width: 10),
_cell(item['endorsement_no'], 3),
SizedBox(width: 10),
_cell(item['sub_type_text'], 2),
SizedBox(width: 10),
_cell(
item['transaction_type'] == 'Credit'
? formatAmount(item['amount'])
: '-',
2,
alignRight: true,
),
SizedBox(width: 10),
_cell(
item['transaction_type'] == 'Debit'
? formatAmount(item['amount'])
: '-',
2,
alignRight: true,
),
SizedBox(width: 10),
_cell(formatAmount(item['balance']), 2, alignRight: true),
SizedBox(width: 10),
_cell(item['description'], 3),
SizedBox(width: 10),
_cell(item['username'], 2),
SizedBox(width: 10),
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isAllowedSubType)
_ActionIconButton(
icon: Icons.picture_as_pdf_outlined,
onTap: () => getCdEndorsementDetails(item['id']),
),
const SizedBox(width: 8),
if (isAllowedSubType && hasSplitUpFile)
_ActionIconButton(
icon: Icons.folder_open_outlined,
onTap: () => _launchURL(item['split_up_url']),
),
],
),
),
],
),
);
}
Widget _cell(String? text, int flex, {bool alignRight = false}) {
return Expanded(
flex: flex,
child: Text(
text ?? '-',
textAlign: alignRight ? TextAlign.right : TextAlign.left,
style: GoogleFonts.poppins(fontSize: 12),
// overflow: TextOverflow.ellipsis,
),
);
}
Widget _buildPagination(BuildContext context) {
final totalPages = (filteredData.length / _rowsPerPage).ceil();
const visiblePageCount = 5;
List<int> getVisiblePages() {
if (totalPages <= visiblePageCount) {
return List.generate(totalPages, (i) => i + 1);
}
if (_currentPage <= 3) {
return [1, 2, 3, 4, 5];
} else if (_currentPage >= totalPages - 2) {
return [
totalPages - 4,
totalPages - 3,
totalPages - 2,
totalPages - 1,
totalPages
];
} else {
return [
_currentPage - 2,
_currentPage - 1,
_currentPage,
_currentPage + 1,
_currentPage + 2,
];
}
}
List<int> visiblePages = getVisiblePages();
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
// Dropdown for rows per page
DropdownButton<int>(
value: _rowsPerPage,
items: [5, 10, 15, 20, 50].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(' $value ',
style: GoogleFonts.poppins(fontSize: 15)),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_rowsPerPage = newValue!;
_currentPage = 1;
});
},
),
// Previous button
IconButton(
onPressed: _currentPage > 1
? () => setState(() => _currentPage--)
: null,
icon: const Icon(Icons.chevron_left),
),
// First page + left ellipsis
if (!visiblePages.contains(1))
Row(children: [
_buildPageButton(1),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text("..."),
),
]),
// Visible page buttons
for (int page in visiblePages) _buildPageButton(page),
// Right ellipsis + last page
if (!visiblePages.contains(totalPages))
Row(children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text("..."),
),
_buildPageButton(totalPages),
]),
// Next button
IconButton(
onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++)
: null,
icon: const Icon(Icons.chevron_right),
),
],
),
),
],
);
}
Widget _buildPageButton(int page) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
_currentPage == page ? const Color(0xFF00A6A6) : Colors.grey[300],
foregroundColor: _currentPage == page ? Colors.white : Colors.black,
minimumSize: const Size(36, 36),
padding: EdgeInsets.zero,
),
onPressed: () {
setState(() {
_currentPage = page;
});
},
child: Text(page.toString()),
),
);
}
}
class _CDTableHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
double get minExtent => 52;
@override
double get maxExtent => 52;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
decoration: BoxDecoration(
color: Color(0xFFD7E9EB),
borderRadius: BorderRadius.circular(6),
),
// color: Color(0xFFD7E9EB),
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: const Row(
children: [
_HeaderCell('Date', 2),
SizedBox(width: 10),
_HeaderCell('Record Date', 2),
SizedBox(width: 10),
_HeaderCell('Unit', 2),
SizedBox(width: 10),
_HeaderCell('Policy', 3),
SizedBox(width: 10),
_HeaderCell('Endorsement No', 3),
SizedBox(width: 10),
_HeaderCell('Sub Type', 2),
SizedBox(width: 10),
_HeaderCell('₹ Credit', 2, alignRight: true),
SizedBox(width: 10),
_HeaderCell('₹ Debit', 2, alignRight: true),
SizedBox(width: 10),
_HeaderCell('₹ Balance', 2, alignRight: true),
SizedBox(width: 10),
_HeaderCell('Description', 3),
SizedBox(width: 10),
_HeaderCell('User', 2),
SizedBox(width: 10),
_HeaderCell('Action', 2, center: true),
SizedBox(width: 10),
],
),
);
}
@override
bool shouldRebuild(_) => false;
}
class _HeaderCell extends StatelessWidget {
final String text;
final int flex;
final bool alignRight;
final bool center;
const _HeaderCell(
this.text,
this.flex, {
this.alignRight = false,
this.center = false,
});
@override
Widget build(BuildContext context) {
return Expanded(
flex: flex,
child: Text(
text,
textAlign:
center ? TextAlign.center : (alignRight ? TextAlign.right : TextAlign.left),
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: const Color(0xFF000000),
),
),
);
}
}
class _ActionIconButton extends StatelessWidget {
final IconData icon;
final VoidCallback onTap;
final String? subType;
final bool enabled;
const _ActionIconButton({
required this.icon,
required this.onTap,
required this.subType,
this.enabled = true,
});
@override
@ -1152,15 +1046,17 @@ class _ActionIconButton extends StatelessWidget {
width: 36,
height: 36,
child: Material(
color: const Color(0xFFDFF4F5), // light teal bg
color: enabled
? const Color(0xFFDFF4F5)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: (subType == '3' || subType == '4') ? onTap : null,
onTap: enabled ? onTap : null,
child: Icon(
icon,
size: 22,
color: Colors.black,
color: enabled ? Colors.black : Colors.transparent,
),
),
),
@ -1169,6 +1065,7 @@ class _ActionIconButton extends StatelessWidget {
}
// Sample Data class representing each element in the array
class Data {
final dynamic value;

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:csv/csv.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
@ -35,6 +36,7 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
dynamic empHrId;
String? _postPreToken = '';
TextEditingController searchController = TextEditingController();
TextEditingController searchClaimsStatusController = TextEditingController();
Map<String, TextEditingController> controllers = {};
List<dynamic> reversedDataPolicy = [];
@ -102,6 +104,22 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
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);
}
});
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
@ -147,6 +165,7 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
getClaimsPoliciesDetails();
getClaimList();
}
@override
void dispose() {
for (var controller in controllers.values) {
@ -282,13 +301,14 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
List<List<String>> rows = [];
// Header
rows.add(['Emp Name', 'Emp Code', 'Policy Type','Client Policy No','claim_no','status','claim_amount','ticket_created_date']);
rows.add(['Emp Name', 'Emp Code','Insured Name', 'Policy Type','Client Policy No','Claim Number','Claim Type','Status','Claim Amount','Record Date']);
// Data rows
for (var item in data) {
rows.add([
item['emp_name'] ?? '',
item['emp_code'] ?? '',
item['insured_name'] ?? '',
item['policy_type'] ?? '',
item['client_policy_no'] ?? '',
item['claim_no'] ?? '',
@ -363,6 +383,10 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
.toString()
.toLowerCase()
.contains(query.toLowerCase()) ||
row['insured_name']
.toString()
.toLowerCase()
.contains(query.toLowerCase()) ||
row['policy_type']
.toString()
.toLowerCase()
@ -419,23 +443,25 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
false;
}
// PUT IT HERE (inside State, outside build)
List<Map<String, dynamic>> get claimStatusList {
final list = getClaimPoliciesApi['claim_status'] ?? [];
if (list is! List) return [];
return list
.map<Map<String, dynamic>>((e) => {
'id': int.parse(e['id'].toString()),
'name': e['claim_status'].toString(),
})
.toList();
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
canPop: false,
child: _buildContent(context),
),
);
@ -457,16 +483,16 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
/// 🔙 Back + Title (LEFT)
Row(
children: [
IconButton(
onPressed: () => {},
icon: const Icon(
Icons.arrow_back_ios,
size: 18,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
// IconButton(
// onPressed: () => {},
// icon: const Icon(
// Icons.arrow_back_ios,
// size: 18,
// color: Colors.black,
// ),
// padding: EdgeInsets.zero,
// constraints: const BoxConstraints(),
// ),
const SizedBox(width: 6),
Text(
'Claims',
@ -588,7 +614,7 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
),
)
: Expanded(
child: _buildClaimsDataTable(context),
child: _buildClaimsDataTable(context), // THIS IS REQUIRED
),
// Expanded(
// child: Container(
@ -631,360 +657,62 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
);
}
Widget _buildClaimStatus(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width * 0.15,
child: buildDropdownFieldSearch(
'Claim Status',
(int? value) {
setState(() {
selectedClaimStatus = value;
});
print('Selected Claim Status ID: $selectedClaimStatus');
},
claimStatusList,
'name',
selectedClaimStatus,
),
);
}
Widget _buildClaimsDataTable(BuildContext context) {
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
return const Center(child: Text('No available data'));
}
return ListView.builder(
itemCount: _paginatedData.length + 2, // +1 for header, +1 for pagination
itemBuilder: (context, index) {
if (index == 0) return _buildHeader();
if (index == _paginatedData.length + 1)
return _buildPagination(context);
return CustomScrollView(
slivers: [
/// 🔒 FIXED HEADER
SliverPersistentHeader(
pinned: true,
delegate: _ClaimsHeaderDelegate(),
),
final item = _paginatedData[index - 1];
return _buildDataRow(item);
},
);
/// 📄 TABLE ROWS
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final item = _paginatedData[index];
return _buildDataRow(item);
},
childCount: _paginatedData.length,
),
),
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // Header row
// Container(
// decoration: BoxDecoration(
// color: Color(0xFF00A6A6),
// borderRadius: BorderRadius.circular(6),
// ),
// padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
// child: Row(
// children: [
// Expanded(
// flex: 4,
// child: Text(
// 'Name',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 4,
// child: Text(
// 'Policy Name',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// 'Claim Number',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 2,
// child: Text(
// 'Status',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// 'Claim Amount',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// 'Record Date',
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// Expanded(
// flex: 2,
// child: Text(
// 'Action',
// textAlign: TextAlign.center,
// style: GoogleFonts.poppins(
// color: Colors.white, fontWeight: FontWeight.bold),
// ),
// ),
// ],
// ),
// ),
//
// const SizedBox(height: 6),
//
// SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: Column(
// children: _paginatedData.mapIndexed((index, item) {
// return Container(
// // margin: const EdgeInsets.only(bottom: 8),
// padding:
// const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
// decoration: BoxDecoration(
// color: Colors.white,
// border: Border(
// bottom: BorderSide(
// // color: Color(0xFFA1A1A1),
// color: Color(0xFFD7E9EB),
// width: 1,
// ),
// ),
// // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white,
// borderRadius: BorderRadius.circular(6),
// ),
// child: Row(
// children: [
// Expanded(
// flex: 4,
// child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// item['emp_name'] ?? '-',
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w400,
// fontSize: 12),
// ),
// Text(
// item['emp_code'] ?? '-',
// style: GoogleFonts.poppins(
// color: Color(0xFF585757),
// fontWeight: FontWeight.w300,
// fontSize: 10),
// ),
// ],
// ),
// ),
// Expanded(
// flex: 4,
// child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// item['emp_name'] ?? '-',
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w400,
// fontSize: 12),
// ),
// Text(
// item['policy_no'] ?? '-',
// style: GoogleFonts.poppins(
// color: Color(0xFF585757),
// fontWeight: FontWeight.w300,
// fontSize: 10),
// ),
// ],
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// "${item['claim_no'] ?? ''}",
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w400,
// fontSize: 12),
// ),
// ),
// Expanded(
// flex: 2,
// child: Container(
// padding: const EdgeInsets.symmetric(
// horizontal: 4, vertical: 4),
// decoration: BoxDecoration(
// color: Color(0xFF7BD9B6),
// // color: (item['emp_is_active'] == "1")
// // ? Color(0xFF7BD9B6)
// // : Color(0xFFFFA6A6),
// borderRadius: BorderRadius.circular(6),
// ),
// child: Align(
// alignment: Alignment.center,
// child: Text(
// "${item['status'] ?? ''}",
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w500,
// fontSize: 12,
// ),
// ),
// ),
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// "${item['claim_amount'] ?? ''}",
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w400,
// fontSize: 12),
// ),
// ),
// Expanded(
// flex: 3,
// child: Text(
// "${item['ticket_created_date'] ?? ''}",
// style: GoogleFonts.poppins(
// color: Color(0xFF000000),
// fontWeight: FontWeight.w400,
// fontSize: 12),
// ),
// ),
// Expanded(
// flex: 2,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// GestureDetector(
// onTap: () {},
// child: Container(
// height: 30,
// width: 30,
// decoration: BoxDecoration(
// color: Color(0xFFE6F5F6),
// borderRadius: BorderRadius.circular(12)),
// child: Icon(Icons.credit_card,
// color: Color(0xFF3D3D3D)),
// // child: Image.asset(
// // 'assets/ecard.jpg',
// // height: 20,
// // fit: BoxFit.cover,
// // ),
// ),
// ),
// ],
// ),
// ),
// ],
// ),
// );
// }).toList(),
// ),
// ),
//
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 12),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// DropdownButton<int>(
// value: _rowsPerPage,
// items: [5, 10, 15, 20, 50].map((int value) {
// return DropdownMenuItem<int>(
// value: value,
// child: Text(
// ' $value ',
// style: GoogleFonts.poppins(fontSize: 15),
// ),
// );
// }).toList(),
// onChanged: (newValue) {
// setState(() {
// _rowsPerPage = newValue!;
// _currentPage =
// 1; // Reset to first page when rows per page changes
// });
// },
// ),
// IconButton(
// onPressed: _currentPage > 1
// ? () {
// setState(() {
// _currentPage--;
// });
// }
// : null,
// icon: Icon(Icons.chevron_left),
// ),
// for (int i = 1;
// i <= (filteredData.length / _rowsPerPage).ceil();
// i++)
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 4),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: _currentPage == i
// ? Color(0xFF00A6A6)
// : Colors.grey[300],
// foregroundColor:
// _currentPage == i ? Colors.white : Colors.black,
// minimumSize: Size(36, 36),
// padding: EdgeInsets.zero,
// ),
// onPressed: () {
// setState(() {
// _currentPage = i;
// });
// },
// child: Text(i.toString()),
// ),
// ),
// IconButton(
// onPressed: _currentPage <
// (filteredData.length / _rowsPerPage).ceil()
// ? () {
// setState(() {
// _currentPage++;
// });
// }
// : null,
// icon: Icon(Icons.chevron_right),
// ),
// ],
// ),
// ),
// ],
// ),
// ],
// );
}
Widget _buildHeader() {
return Container(
decoration: BoxDecoration(
color: Color(0xFF00A6A6),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: const Row(
children: [
Expanded(flex: 4, child: Text('Name', style: _headerStyle)),
Expanded(flex: 4, child: Text('Policy Name', style: _headerStyle)),
Expanded(flex: 3, child: Text('Claim Number', style: _headerStyle)),
Expanded(flex: 4, child: Text('Status', style: _headerStyle)),
Expanded(flex: 3, child: Text('Claim Amount', style: _headerStyle)),
Expanded(flex: 3, child: Text('Record Date', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Action',
textAlign: TextAlign.center, style: _headerStyle)),
],
),
/// 📌 PAGINATION
SliverToBoxAdapter(
child: _buildPagination(context),
),
],
);
}
Widget _buildDataRow(Map<String, dynamic> item) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
@ -1008,6 +736,15 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
],
),
),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item['insured_name'] ?? '-', style: _dataBold),
],
),
),
Expanded(
flex: 4,
child: Column(
@ -1556,7 +1293,7 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
lastDate: DateTime.now(),
);
if (pickedDate != null) {
@ -1566,6 +1303,7 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
print("FomatedFromDAta - $formattedDate");
setState(() {
controllers['from']?.text = formattedDate;
controllers['to']?.clear(); // 🔥 prevent invalid To date
});
}
},
@ -1633,27 +1371,30 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
child: TextField(
controller: controllers['to'],
readOnly: true,
onTap: () async {
FocusScope.of(context)
.requestFocus(FocusNode()); // hide keyboard
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
onTap: () async {
FocusScope.of(context).requestFocus(FocusNode());
if (pickedDate != null) {
String formattedDate =
"${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}";
final fromDate = _parseDate(controllers['from']?.text);
print("FomatedFromDAta - $formattedDate");
setState(() {
controllers['to']?.text = formattedDate;
});
}
},
style: const TextStyle(
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: fromDate ?? DateTime.now(),
firstDate: fromDate ?? DateTime(2000), // cannot be before From date
lastDate: DateTime.now(), // no future dates
);
if (pickedDate != null) {
String formattedDate =
"${pickedDate.day.toString().padLeft(2, '0')}-"
"${pickedDate.month.toString().padLeft(2, '0')}-"
"${pickedDate.year}";
setState(() {
controllers['to']?.text = formattedDate;
});
}
},
style: const TextStyle(
fontSize: 12,
),
decoration: InputDecoration(
@ -1688,138 +1429,119 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
);
}
Widget _buildClaimStatus(BuildContext context) {
List<dynamic> claimStatusList = getClaimPoliciesApi['claim_status'] ?? [];
Widget buildDropdownFieldSearch(
String label,
void Function(int?) onChanged,
List<Map<String, dynamic>> itemsList,
String displayField,
int? selectedValue,
) {
// Get type_name from ticket_type id
String? getTypeNameById(int? id) {
if (id == null) return null;
final match = claimStatusList.firstWhere(
(e) => e['id'] == id,
orElse: () => null,
);
return match != null ? match['claim_status'] : null;
}
// Get id from type_name
int? getIdByTypeName(String? typeName) {
print("getIdByTypeName");
print("getIdByTypeName - $typeName");
final match = claimStatusList.firstWhere(
(e) => e['claim_status'] == typeName,
orElse: () => null,
);
print("match - $match");
if (match != null && match is Map) {
final map = match as Map;
final id = map['id'];
print("Returning id: $id");
return id is int ? id : int.tryParse(id.toString());
}
print("no match or invalid map");
return null;
}
return Container(
width: MediaQuery.of(context).size.width * 0.15,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Claim Status",
style: TextStyle(fontSize: 12),
),
const SizedBox(
height: 3,
),
SizedBox(
height: 40,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08), // soft shadow
blurRadius: 8,
offset: const Offset(0, 2), // downward shadow
),
],
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
const SizedBox(height: 4),
Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<int>(
isExpanded: true,
value: selectedValue,
hint: const Text(
'Select',
style: TextStyle(fontSize: 12),
),
child: DropdownSearch<String>(
selectedItem: selectedClaimStatusName,
items: (String? filter, _) {
return claimStatusList
.map<String>((item) => item['claim_status'].toString())
.toList();
},
onChanged: (value) {
setState(() {
selectedClaimStatusName = value;
selectedClaimStatus = getIdByTypeName(value);
});
print(
"Selected selectedClaimStatusName: $selectedClaimStatusName");
print("Selected selectedClaimStatus: $selectedClaimStatus");
iconStyleData: const IconStyleData(
icon: Icon(Icons.keyboard_arrow_down),
),
dropdownStyleData: DropdownStyleData(
maxHeight: 260,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
// 🔍 SEARCH SUPPORT
dropdownSearchData: DropdownSearchData(
searchController: searchClaimsStatusController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: searchClaimsStatusController,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
hintText: 'Search...',
hintStyle: const TextStyle(fontSize: 12),
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
searchMatchFn: (item, searchValue) {
final text = item.child.toString().toLowerCase();
return text.contains(searchValue.toLowerCase());
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
searchClaimsStatusController.clear();
}
},
dropdownBuilder: (context, selectedItem) {
return Text(
selectedItem ?? "",
style: TextStyle(fontSize: 12),
items: itemsList.map((item) {
return DropdownMenuItem<int>(
value: item['id'],
child: Text(
item[displayField],
style: const TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
);
},
decoratorProps: DropDownDecoratorProps(
decoration: InputDecoration(
// suffixStyle: TextStyle(color: Colors.red),
hintText: "Select Policy Type",
hintStyle: TextStyle(fontSize: 12),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.teal, width: 2),
),
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 10),
labelStyle: TextStyle(fontSize: 19, color: Colors.red),
),
),
popupProps: const PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
showSearchBox: true,
searchFieldProps: TextFieldProps(
style: TextStyle(fontSize: 14.0, color: Colors.red),
decoration: InputDecoration(
labelStyle: TextStyle(fontSize: 19, color: Colors.red),
hintText: "Search Claim Status",
hintStyle: TextStyle(fontSize: 12),
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 10),
),
),
),
}).toList(),
onChanged: onChanged,
),
)
),
],
),
),
],
);
}
DateTime? _parseDate(String? value) {
if (value == null || value.isEmpty) return null;
final parts = value.split('-');
return DateTime(
int.parse(parts[2]),
int.parse(parts[1]),
int.parse(parts[0]),
);
}
Widget _buildPolicyNumber(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.15,
@ -1920,6 +1642,54 @@ class _ClaimsPolicieState extends State<ClaimsPolicies> {
}
class _ClaimsHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
double get minExtent => 52;
@override
double get maxExtent => 52;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
// color: const Color(0xFF00A6A6),
decoration: BoxDecoration(
color: Color(0xFF00A6A6),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: const Row(
children: [
Expanded(flex: 4, child: Text('Name', style: _headerStyle)),
Expanded(flex: 3, child: Text('Insured Name', style: _headerStyle)),
Expanded(flex: 4, child: Text('Policy Name', style: _headerStyle)),
Expanded(flex: 3, child: Text('Claim Number', style: _headerStyle)),
Expanded(flex: 4, child: Text('Status', style: _headerStyle)),
Expanded(flex: 3, child: Text('Claim Amount', style: _headerStyle)),
Expanded(flex: 3, child: Text('Record Date', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Action',
textAlign: TextAlign.center, style: _headerStyle),
),
],
),
);
}
static const _headerStyle = TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
);
@override
bool shouldRebuild(_) => false;
}
// ================= ICON BUTTON =================
class _IconActionButton extends StatelessWidget {
final IconData icon;

View File

@ -1,68 +1,65 @@
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;
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);
hrDashboard({Key? key}) : super(key: key);
@override
State<hrDashboard> createState() => _hrDashboardState();
}
class _hrDashboardState extends State<hrDashboard>
with SingleTickerProviderStateMixin {
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>{};
String? _metabaseToken;
String? _metabaseUrl;
bool isPolicyLoading = false;
bool isDashboardLoading = false;
bool hasDashboardError = false;
bool _metabaseLoaded = false; // FIX
bool _metabaseLoaded = false;
List<int> postModules = [];
dynamic policy_name;
dynamic getPreCardArrays = [];
dynamic getPostCardArrays = [];
String? _postPreToken = '';
String _dashboardViewType = '';
final tokenService = TokenStorageService();
final FocusNode _policyFocusNode = FocusNode();
// Variables for API data
dynamic empClientBranchId;
dynamic empHrId;
dynamic empClientId;
String? _postPreToken = '';
int stausVal = 1;
String _dashboardViewType = '';
bool isDropdownOpen = false;
final FocusNode _policyFocusNode = FocusNode();
final tokenService = TokenStorageService();
bool _isPolicyDropdownOpen = false;
@override
void initState() {
super.initState();
apiService = ApiService(context);
_policyFocusNode.addListener(() {
if (!_policyFocusNode.hasFocus) {
setState(() => isDropdownOpen = false);
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();
}
@ -80,13 +77,8 @@ class _hrDashboardState extends State<hrDashboard>
? List<int>.from(jsonDecode(postRaw))
: [];
if (!(postModules.contains(2) ||
postModules.contains(3) ||
postModules.contains(4))) {
// No dashboard permission
setState(() {
hasDashboardError = true;
});
if (!(postModules.contains(2) || postModules.contains(3) || postModules.contains(4))) {
setState(() => hasDashboardError = true);
return;
}
@ -94,103 +86,38 @@ class _hrDashboardState extends State<hrDashboard>
empClientBranchId = await tokenService.readValue('empClientBranchId');
empHrId = await tokenService.readValue('empHrId');
await getPostCashDepositDetails(
empClientBranchId, empClientId, empHrId, _postPreToken);
await getPostCashDepositDetails(empClientBranchId, empClientId, empHrId, _postPreToken);
}
Future<void> getPostCashDepositDetails(
empClientBranchId, empClientId, empHrId, _postPreToken) async {
print('IN');
print("clintBranchId -$empClientBranchId");
print("clintID -$empClientId");
print("hr_id -$empHrId");
print("token -$_postPreToken");
isLoading = true;
// setState(() {
// _isLoading = true;
// });
Future<void> getPostCashDepositDetails(branchId, clientId, hrId, token) async {
setState(() => isLoading = true);
try {
if (empClientBranchId == null || empClientId == null) {
return;
}
if (branchId == null || clientId == null) return;
final response = await apiService.getActiveCashDepositDetailsToApi(
empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal);
clientId, branchId, hrId, token, stausVal);
// final response = await apiService.getCashDepositDetailsToApi(
// clintID!, clintBranchId!, hr_id, token);
print('IN1');
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);
setState(() {
activePoliciesList = list;
});
// AUTO SELECT FIRST POLICY HERE
if (list.isNotEmpty) {
selectedPolicyId = list.first['client_policy_id'].toString();
await _loadDashboardByPolicy(selectedPolicyId!);
} else {
setState(() {
hasDashboardError = true;
});
setState(() => hasDashboardError = true);
}
} else {
isLoading = false;
print('API request failed with status');
setState(() {
activePoliciesList = [];
});
print('API request failed with status');
}
} catch (e) {
print('Exception occurred: $e');
}
}
Future<void> _loadMetabaseDashboard() async {
try {
final response = await http.get(
Uri.parse(
'${Environment.apiUrlPost.replaceAll("employeeRest/", "")}metaDashboardDemo?api=1',
),
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
_metabaseToken = json['data']['metabaseToken'];
_metabaseUrl = json['data']['metabaseUrl'];
final htmlContent = _buildMetabaseHtml(
token: _metabaseToken!,
url: _metabaseUrl!,
);
final iframe = html.IFrameElement()
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..style.minHeight = '100vh'
..srcdoc = htmlContent;
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'metabase-dashboard',
(int viewId) => iframe,
);
setState(() {
_metabaseLoaded = true;
});
} else {
throw Exception('Failed to load Metabase');
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Metabase loading failed');
debugPrint(e.toString());
debugPrint('Exception occurred: $e');
} finally {
setState(() => isLoading = false);
}
}
@ -212,207 +139,93 @@ class _hrDashboardState extends State<hrDashboard>
url: response['data']['metabaseUrl'],
clientPolicyId: clientPolicyId,
);
setState(() {
_metabaseLoaded = true;
});
setState(() => _metabaseLoaded = true);
} else {
ToastHelper.showErrorToast(context, response['message']);
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Dashboard loading failed');
} finally {
setState(() {
isDashboardLoading = false;
});
setState(() => isDashboardLoading = false);
}
}
void _registerMetabaseIframe({
required String token,
required String url,
required String clientPolicyId,
}) {
_dashboardViewType = 'metabase-dashboard-$clientPolicyId';
final viewType = 'metabase-dashboard-$clientPolicyId';
_dashboardViewType = viewType;
final htmlContent = _buildMetabaseHtml(
token: token,
url: url,
);
if (_registeredViewTypes.contains(viewType)) return;
final iframe = html.IFrameElement()
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..style.minHeight = '100vh'
..srcdoc = htmlContent;
final embedUrl = "$url/embed/dashboard/$token#theme=light&bordered=true&titled=true";
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
_dashboardViewType,
(int viewId) => iframe,
viewType,
(int viewId) => html.IFrameElement()
..src = embedUrl
..style.border = 'none'
..style.width = '100%'
..style.height = '100%'
..allowFullscreen = true,
);
}
Future<bool> _showLogoutDialog() async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) =>
AlertDialog(
title: Text("Confirm Logout"),
content: Text("Do you want to logout?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text("Cancel"),
),
TextButton(
onPressed: () => apiService.logout(),
child: Text("Logout"),
),
],
),
) ??
false;
_registeredViewTypes.add(viewType);
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: _buildContent(context),
);
return BaseLayout(child: _buildContent(context));
}
Widget _buildContent(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
await tokenService.clearAll();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
}
},
child: Scaffold(
body: Expanded(
// REMOVED 'Expanded' from directly inside body.
body: Container(
width: double.infinity,
height: double.infinity,
child: isDashboardLoading
? Center(
child: Image.asset(
'assets/nhance-loader.gif',
height: 60,
width: 60,
),
)
? _buildLoader()
: _metabaseLoaded
? (postModules.isNotEmpty && activePoliciesList.isEmpty) ?
Padding(
padding: EdgeInsets.all(16),
child: Text(
'No dashboard data available for your account',
style: TextStyle(color: Colors.grey),
),
) : Container(
decoration: BoxDecoration(
// color: Colors.red.shade50,
// color: Colors.white,
borderRadius: BorderRadius.circular(10), // 👈 set your desired radius
),
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 🔹 TOP BAR (always clickable)
Material(
elevation: 2,
color: Colors.white,
child: _buildPolicySelector(),
),
SizedBox(height: 20),
// 🔹 DASHBOARD AREA (isolated)
Expanded(
child: Stack(
children: [
Positioned.fill(
child: _buildDashboard(),
),
],
),
),
],
)
)
: const Center(
child: Text(
'No dashboard data available',
style: TextStyle(color: Colors.grey),
),
),
? _buildDashboardView()
: _buildEmptyState(),
),
),
);
}
Widget _buildPolicySelector() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Row(
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: [
const Text(
'Select Policy',
style: TextStyle(fontWeight: FontWeight.w600),
// Policy Selector
Material(
elevation: 2,
borderRadius: BorderRadius.circular(8),
child: _buildPolicySelector(),
),
const SizedBox(width: 12),
SizedBox(
width: 420,
child: DropdownButtonFormField<String>(
focusNode: _policyFocusNode,
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(),
onTap: () {
setState(() => isDropdownOpen = true);
},
onChanged: isDashboardLoading
? null
: (value) {
if (value == selectedPolicyId) return;
setState(() {
selectedPolicyId = value!;
});
_loadDashboardByPolicy(value!);
},
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
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,
),
),
),
@ -422,69 +235,240 @@ SizedBox(height: 20),
);
}
Widget _buildDashboard() {
if (isDashboardLoading) {
return Center(
child: Image.asset(
'assets/nhance-loader.gif',
height: 60,
width: 60,
),
);
}
// 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();
// },
// ),
// ),
// ],
// ),
// );
// }
if (!_metabaseLoaded || _dashboardViewType.isEmpty) {
return const Center(
child: Text(
'No dashboard data available',
style: TextStyle(color: Colors.grey),
),
);
}
// 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)),
// ),
// ),
// ),
// ],
// ),
// );
// }
return HtmlElementView(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(
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),
);
String _buildMetabaseHtml({
required String token,
required String url,
}) {
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Metabase Dashboard</title>
Widget _buildEmptyState() => const Center(
child: Text('No dashboard data available', style: TextStyle(color: Colors.grey)),
);
<script defer src="$url/app/embed.js"></script>
<script>
window.metabaseConfig = {
theme: { preset: "light" },
isGuest: true,
instanceUrl: "$url"
};
</script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<metabase-dashboard
token="$token"
with-title="true"
with-downloads="true">
</metabase-dashboard>
</body>
</html>
''';
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart';
import 'package:nhancepolicy/presentation/claims.dart';
import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:nhancepolicy/service/_ResponsiveGridConfig.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import 'package:intl/intl.dart';
@ -298,7 +299,7 @@ class _policiesState extends State<policies>
Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
@ -331,12 +332,16 @@ class _policiesState extends State<policies>
Widget build(BuildContext context) {
return BaseLayout(
child: PopScope(
canPop: false, // 🚫 block default back
canPop: false,
onPopInvoked: (didPop) async {
bool logout = await _showLogoutDialog();
if (logout) {
if (didPop) return;
final shouldLogout = await _showLogoutDialog();
if (shouldLogout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
@ -369,18 +374,18 @@ class _policiesState extends State<policies>
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () {
_showLogoutDialog();
},
icon: const Icon(
Icons.arrow_back_ios,
size: 20,
color: Colors.black,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
// IconButton(
// onPressed: () {
// _showLogoutDialog();
// },
// icon: const Icon(
// Icons.arrow_back_ios,
// size: 20,
// color: Colors.black,
// ),
// padding: EdgeInsets.zero,
// constraints: const BoxConstraints(),
// ),
const SizedBox(width: 5),
Text(
'Policies',
@ -396,7 +401,7 @@ class _policiesState extends State<policies>
SizedBox(height: 15),
Container(
width: double.infinity,
height: 300,
height: 400,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
@ -405,28 +410,21 @@ class _policiesState extends State<policies>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// ================= OPEN FOR ENROLLMENT =================
const Text(
Text(
'Open for Enrollment',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(height: 14),
SizedBox(height: 14),
Container(
width: double.infinity,
// padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(6),
// border: Border.all(color: Colors.black12),
),
/// SCROLLABLE AREA
Expanded(
child: openEnrollment.isEmpty
? _EmptyBox('No policies open for enrollment')
: _PolicyGrid(
policies: openEnrollment, isEnrollment: true),
policies: openEnrollment,
isEnrollment: true,
),
),
const SizedBox(height: 24),
],
),
),
@ -435,7 +433,7 @@ class _policiesState extends State<policies>
SizedBox(height: 20),
Container(
width: double.infinity,
height: 300,
height: 400,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
@ -444,14 +442,13 @@ class _policiesState extends State<policies>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// ================= ACTIVE POLICIES HEADER =================
/// HEADER
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
Text(
'Active Policies',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600),
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600),
),
_ActiveExpiredToggle(
selectedIndex: selectedIndex,
@ -460,13 +457,6 @@ class _policiesState extends State<policies>
selectedIndex = value;
stausVal = value == 1 ? 1 : 0;
});
empClientId =
await tokenService.readValue('empClientId');
empClientBranchId =
await tokenService.readValue('empClientBranchId');
empHrId = await tokenService.readValue('empHrId');
await getPostCashDepositDetails(
empClientBranchId,
empClientId,
@ -479,36 +469,29 @@ class _policiesState extends State<policies>
),
const SizedBox(height: 14),
Container(
width: double.infinity,
// padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(6),
// border: Border.all(color: Colors.black12),
),
/// SCROLLABLE GRID
Expanded(
child: activePolicies.isEmpty
? _EmptyBox('No Active policies')
? stausVal == 0 ? _EmptyBox('You dont have any expired policies at the moment.') : _EmptyBox('No active policies found')
: _PolicyGrid(
policies: activePolicies,
isEnrollment: false,onBulkDownload: (clientPolicyId) {
getEcardBulkDownload(clientPolicyId);
}
policies: activePolicies,
isEnrollment: false,
onBulkDownload: getEcardBulkDownload,
),
),
const SizedBox(height: 10),
const Align(
const SizedBox(height: 8),
Align(
alignment: Alignment.bottomRight,
child: Text(
'* Premium may vary subject to claims',
style: TextStyle(fontSize: 10, color: Colors.red),
style: GoogleFonts.poppins(fontSize: 10, color: Colors.red),
),
),
],
),
),
]
],
@ -518,6 +501,16 @@ class _policiesState extends State<policies>
}
}
class ResponsiveGridConfig {
final int crossAxisCount;
final double childAspectRatio;
const ResponsiveGridConfig(
this.crossAxisCount,
this.childAspectRatio,
);
}
class _PolicyGrid extends StatelessWidget {
final List<Map<String, dynamic>> policies;
final bool isEnrollment;
@ -530,18 +523,37 @@ class _PolicyGrid extends StatelessWidget {
this.onBulkDownload,
});
ResponsiveGridConfig _getGridConfig(
BuildContext context,
bool isEnrollment,
) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return ResponsiveGridConfig(1, isEnrollment ? 1.25 : 1.15);
} else if (width < 900) {
return ResponsiveGridConfig(2, isEnrollment ? 1.6 : 1.45);
} else if (width < 1400) {
return ResponsiveGridConfig(3, isEnrollment ? 3.1 : 2.5);
} else {
return ResponsiveGridConfig(4, isEnrollment ? 3.1 : 2.4);
}
}
@override
Widget build(BuildContext context) {
final tokenService = TokenStorageService();
final config = _getGridConfig(context, isEnrollment);
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // desktop
crossAxisCount: config.crossAxisCount,
childAspectRatio: config.childAspectRatio,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: isEnrollment ? 2.8 : 2.2,
),
itemCount: policies.length,
itemBuilder: (context, index) {
@ -550,78 +562,84 @@ class _PolicyGrid extends StatelessWidget {
return isEnrollment
? _EnrollmentPolicyCardNew(
data: data,
onTap: () async {
final String? token = await tokenService.getCurrentToken();
final String? enrollmentClientId = await tokenService.readValue('enrollmentClient_id');
final String? enrollmentBranchId = await tokenService.readValue('enrollmentEmpClientBranchId');
onTap: () async {
final token = await tokenService.getCurrentToken();
final clientId =
await tokenService.readValue('enrollmentClient_id');
final branchId = await tokenService
.readValue('enrollmentEmpClientBranchId');
// SAFETY CHECK
if (token == null ||
enrollmentClientId == null ||
enrollmentBranchId == null) {
debugPrint('❌ Missing required data for navigation ${token}');
return;
}
if (token == null || clientId == null || branchId == null) {
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => hrPolicyDetails(
ClientId: enrollmentClientId, // now String
policyTypeId: data['policy_type_id'].toString(),
ClientPoliyId: data['client_policy_id'].toString(),
clientBranchId: enrollmentBranchId, // now String
Token: token, // now String
TokenType: "pre",
cardType: data['type'].toString(),
cardPolicyNo: data['policy_no'].toString(),
cardInsurer_name: data['insurer_short_name'].toString(),
cardPolicy_name: data['policy_name'].toString(),
cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
total_premium: '',
is_ecard_bulk_download_for_employee: 0
),
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: 'hrPolicyDetails'),
builder: (_) => hrPolicyDetails(
ClientId: clientId,
policyTypeId:
data['policy_type_id'].toString(),
ClientPoliyId:
data['client_policy_id'].toString(),
clientBranchId: branchId,
Token: token,
TokenType: "pre",
cardType: data['type'].toString(),
cardPolicyNo: data['policy_no'].toString(),
cardInsurer_name:
data['insurer_short_name'].toString(),
cardPolicy_name:
data['policy_name'].toString(),
cardPolicy_ExpDate:
data['policy_expiry_date'].toString(),
total_premium: '',
is_ecard_bulk_download_for_employee: 0,
),
);
},
),
);
},
)
: _ActivePolicyCardNew(
data: data,
onBulkDownload: onBulkDownload,
onTap: () async {
final String? token = await tokenService.getCurrentToken();
final String? empClientId = await tokenService.readValue('empClientId');
final String? empBranchId = await tokenService.readValue('empClientBranchId');
final token = await tokenService.getCurrentToken();
final clientId =
await tokenService.readValue('empClientId');
final branchId =
await tokenService.readValue('empClientBranchId');
// SAFETY CHECK
if (token == null ||
empClientId == null ||
empBranchId == null) {
debugPrint('❌ Missing required data for navigation ${token}');
if (token == null || clientId == null || branchId == null) {
return;
}
print('$token , $empClientId, $empBranchId');
print(data);
// return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => hrPolicyDetails(
ClientId: empClientId, // <-- from map
policyTypeId: data['policy_type_id'].toString(), // <-- from map
ClientPoliyId: data['client_policy_id'].toString(),
clientBranchId: empBranchId,
settings: const RouteSettings(name: 'hrPolicyDetails'),
builder: (_) => hrPolicyDetails(
ClientId: clientId,
policyTypeId:
data['policy_type_id'].toString(),
ClientPoliyId:
data['client_policy_id'].toString(),
clientBranchId: branchId,
Token: token,
TokenType: 'post',
cardType: data['type'].toString(),
cardPolicyNo: data['policy_no'].toString(),
cardInsurer_name: data['insurer_short_name'].toString(),
cardPolicy_name: data['policy_name'].toString(),
cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
total_premium: data['total_premium'].toString(),
is_ecard_bulk_download_for_employee : data['is_ecard_bulk_download_for_employee'],
cardInsurer_name:
data['insurer_short_name'].toString(),
cardPolicy_name:
data['policy_name'].toString(),
cardPolicy_ExpDate:
data['policy_expiry_date'].toString(),
total_premium:
data['total_premium'].toString(),
is_ecard_bulk_download_for_employee:
data['is_ecard_bulk_download_for_employee'],
),
),
);
@ -632,6 +650,120 @@ class _PolicyGrid extends StatelessWidget {
}
}
// class _PolicyGrid extends StatelessWidget {
// final List<Map<String, dynamic>> policies;
// final bool isEnrollment;
// final Function(String clientPolicyId)? onBulkDownload;
//
// const _PolicyGrid({
// super.key,
// required this.policies,
// required this.isEnrollment,
// this.onBulkDownload,
// });
//
// @override
// Widget build(BuildContext context) {
// final tokenService = TokenStorageService();
//
// return GridView.builder(
// physics: const BouncingScrollPhysics(), // scroll enabled
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 4, // desktop
// crossAxisSpacing: 16,
// mainAxisSpacing: 16,
// childAspectRatio: isEnrollment ? 2.8 : 2.4,
// ),
// itemCount: policies.length,
// itemBuilder: (context, index) {
// final data = policies[index];
//
// return isEnrollment
// ? _EnrollmentPolicyCardNew(
// data: data,
// onTap: () async {
// final String? token = await tokenService.getCurrentToken();
// final String? enrollmentClientId = await tokenService.readValue('enrollmentClient_id');
// final String? enrollmentBranchId = await tokenService.readValue('enrollmentEmpClientBranchId');
//
// // SAFETY CHECK
// if (token == null ||
// enrollmentClientId == null ||
// enrollmentBranchId == null) {
// debugPrint('❌ Missing required data for navigation ${token}');
// return;
// }
//
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => hrPolicyDetails(
// ClientId: enrollmentClientId, // now String
// policyTypeId: data['policy_type_id'].toString(),
// ClientPoliyId: data['client_policy_id'].toString(),
// clientBranchId: enrollmentBranchId, // now String
// Token: token, // now String
// TokenType: "pre",
// cardType: data['type'].toString(),
// cardPolicyNo: data['policy_no'].toString(),
// cardInsurer_name: data['insurer_short_name'].toString(),
// cardPolicy_name: data['policy_name'].toString(),
// cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
// total_premium: '',
// is_ecard_bulk_download_for_employee: 0
// ),
// ),
// );
// },
// )
// : _ActivePolicyCardNew(
// data: data,
// onBulkDownload: onBulkDownload,
// onTap: () async {
// final String? token = await tokenService.getCurrentToken();
// final String? empClientId = await tokenService.readValue('empClientId');
// final String? empBranchId = await tokenService.readValue('empClientBranchId');
//
// // SAFETY CHECK
// if (token == null ||
// empClientId == null ||
// empBranchId == null) {
// debugPrint('❌ Missing required data for navigation ${token}');
// return;
// }
//
// print('$token , $empClientId, $empBranchId');
// print(data);
// // return;
//
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => hrPolicyDetails(
// ClientId: empClientId, // <-- from map
// policyTypeId: data['policy_type_id'].toString(), // <-- from map
// ClientPoliyId: data['client_policy_id'].toString(),
// clientBranchId: empBranchId,
// Token: token,
// TokenType: 'post',
// cardType: data['type'].toString(),
// cardPolicyNo: data['policy_no'].toString(),
// cardInsurer_name: data['insurer_short_name'].toString(),
// cardPolicy_name: data['policy_name'].toString(),
// cardPolicy_ExpDate: data['policy_expiry_date'].toString(),
// total_premium: data['total_premium'].toString(),
// is_ecard_bulk_download_for_employee : data['is_ecard_bulk_download_for_employee'],
// ),
// ),
// );
// },
// );
// },
// );
// }
// }
class _EnrollmentPolicyCardNew extends StatelessWidget {
final Map<String, dynamic> data;
final VoidCallback? onTap;
@ -658,31 +790,45 @@ class _EnrollmentPolicyCardNew extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Policy Number
Text(
data['policy_no'] ?? '',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
Tooltip(
message: '${data['type']} - ${data['policy_no']}',
waitDuration: const Duration(milliseconds: 300),
child: Text(
'${data['type']} - ${data['policy_no']}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 4),
// const SizedBox(height: 4),
//
// /// Insurer
// Tooltip(
// message: data['insurer_name'] ?? '',
// waitDuration: const Duration(milliseconds: 300),
// child: Text(
// data['insurer_name'] ?? '',
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// softWrap: false,
// style: GoogleFonts.poppins(
// fontSize: 11,
// color: Colors.grey,
// ),
// ),
// ),
/// Insurer
Text(
data['insurer_name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 4),
const SizedBox(height: 10),
/// Closes On
Text(
'Closes on: ${data['policy_expiry_date'] ?? ''}',
style: const TextStyle(
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.red,
),
@ -700,7 +846,7 @@ class _EnrollmentPolicyCardNew extends StatelessWidget {
color: Colors.orange,
),
_StatusPillCount(
label: 'Enrolled',
label: 'Under Process',
value: data['membersCountOfEnrolled'] ?? 0,
color: Colors.blue,
),
@ -782,8 +928,6 @@ class _ActivePolicyCardNew extends StatelessWidget {
),
),
),
],
),
@ -795,10 +939,10 @@ class _ActivePolicyCardNew extends StatelessWidget {
children: [
Expanded(
child: Text(
data['policy_no'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
'${data['type']} - ${data['policy_no']}',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
@ -808,12 +952,19 @@ class _ActivePolicyCardNew extends StatelessWidget {
const SizedBox(height: 4),
/// INSURER
Text(
data['insurer_name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
/// Insurer
Tooltip(
message: data['insurer_name'] ?? '',
waitDuration: const Duration(milliseconds: 300),
child: Text(
data['insurer_name'] ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.grey,
),
),
),
@ -822,7 +973,7 @@ class _ActivePolicyCardNew extends StatelessWidget {
/// DATE RANGE
Text(
'${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}',
style: const TextStyle(
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF8A9B0F),
),
@ -878,12 +1029,12 @@ class _StatusPillCount extends StatelessWidget {
children: [
Text(
label,
style: TextStyle(fontSize: 13, color: color),
style: GoogleFonts.poppins(fontSize: 13, color: color),
),
const SizedBox(width: 8),
Text(
value.toString(),
style: TextStyle(
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color,
@ -953,7 +1104,7 @@ class _ToggleItem extends StatelessWidget {
),
child: Text(
label,
style: TextStyle(
style: GoogleFonts.poppins(
fontSize: 11,
color: active ? Colors.white : Colors.black,
fontWeight: FontWeight.w500,
@ -980,7 +1131,7 @@ class _EmptyBox extends StatelessWidget {
border: Border.all(color: Colors.black12),
),
child: Center(
child: Text(text, style: const TextStyle(color: Colors.grey)),
child: Text(text, style: GoogleFonts.poppins(color: Colors.grey)),
),
);
}

View File

@ -246,8 +246,11 @@ class _postFileUploadState extends State<postFileUpload> {
Future<void> getHrFileDownload(id, file_name) async {
// final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token);
print("**********-------*****");
final encryptClientId = widget.ClientId;
print(encryptClientId);
final apiurl = Environment.apiUrlPost;
final String url = '$apiurl/hrFileDownload?id=$id';
final String url = '$apiurl/hrFileDownload?id=$id&cliend_id=$encryptClientId';
final token = widget.Token;
final response = await http.get(
@ -548,6 +551,7 @@ class _postFileUploadState extends State<postFileUpload> {
Row(
children: [
IconButton(
tooltip: 'Previous Page',
onPressed: () => {Navigator.pop(context)},
icon: const Icon(
Icons.arrow_back_ios,
@ -1143,6 +1147,7 @@ class _postFileUploadState extends State<postFileUpload> {
// Previous button
IconButton(
tooltip: 'Previous Page',
onPressed: _currentPage > 1
? () => setState(() => _currentPage--)
: null,

View File

@ -734,6 +734,7 @@ class _excelVerifyState extends State<preFileUpload> {
Row(
children: [
IconButton(
tooltip: 'Previous Page',
onPressed: () => {Navigator.pop(context)},
icon: const Icon(
Icons.arrow_back_ios,

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class _ResponsiveGridConfig {
final int crossAxisCount;
final double childAspectRatio;
_ResponsiveGridConfig(this.crossAxisCount, this.childAspectRatio);
}
_ResponsiveGridConfig _getGridConfig(
BuildContext context,
bool isEnrollment,
) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
// Mobile
return _ResponsiveGridConfig(
1,
isEnrollment ? 1.25 : 1.15,
);
} else if (width < 900) {
// Tablet
return _ResponsiveGridConfig(
2,
isEnrollment ? 1.6 : 1.45,
);
} else if (width < 1200) {
// Small desktop
return _ResponsiveGridConfig(
3,
isEnrollment ? 2.1 : 1.9,
);
} else {
// Large desktop
return _ResponsiveGridConfig(
4,
isEnrollment ? 2.8 : 2.4,
);
}
}

View File

@ -19,22 +19,22 @@ class SvgService {
''';
static const String claims = '''
<svg width="32" height="28" viewBox="0 0 32 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.07666 9.87036L10.2632 10.4778L10.2847 10.4885L10.3062 10.4973L11.9312 11.1448L11.9556 11.1545L11.981 11.1624L13.4263 11.5667L13.4556 11.5745L13.4868 11.5793L15.5054 11.8811L15.5884 11.9553V17.5676L15.1685 18.9983L14.7476 19.9016L13.979 21.0491L13.0288 22.0549L12.3452 22.6682L10.8696 23.679L9.28467 24.468L9.02881 24.5833H8.61865L7.02881 23.8333L5.81396 23.0549L4.94678 22.3547L4.51416 21.967L3.74268 21.1985L3.06689 20.2141L2.38135 18.8303L2.04443 17.6985L1.96338 17.1184V11.9553L2.04541 11.8811L4.06494 11.5793L4.1001 11.5745L4.13428 11.5637L6.30127 10.9172L6.32764 10.9084L6.35303 10.8977L8.06885 10.1702L8.09326 10.1594L8.11768 10.1458L8.69482 9.82153L9.07666 9.87036Z" stroke="white"/>
<path d="M28.7302 7.65747H26.114L26.0388 7.61255L26.0261 7.58911V5.23267L28.7302 7.65747Z" stroke="white" stroke-width="2"/>
<path d="M12.5636 14.3857L12.6613 14.5166L12.6515 14.5615L12.2179 15.0137L11.7882 15.3984L10.9923 16.0342L10.9679 16.0537L10.9464 16.0762L10.3292 16.7051L9.54797 17.3291L9.53625 17.3379L9.52551 17.3477L9.07434 17.752L8.08118 18.6416L7.77258 18.9189H7.75208L7.67786 18.8984L7.31067 18.6084L6.9425 18.3164L6.14954 17.5273L6.14075 17.5176L6.13098 17.5088L5.12317 16.6055V16.4863L5.26672 16.3584H5.33411L7.26868 18.0918L7.46008 18.2637L7.71204 18.208L8.07239 18.127L8.20032 18.0986L8.297 18.0107L12.3693 14.3604L12.5636 14.3857Z" stroke="white"/>
<path d="M20.1507 10.2754H27.1021L27.4633 10.599L27.373 11.1654L27.1021 11.3272H20.1507L19.8799 11.0845V10.5181L20.1507 10.2754Z" fill="white"/>
<path d="M17.6227 10.0332H18.2547L18.7061 10.3569L18.7963 10.5187V11.166L18.345 11.5705L18.1644 11.6514H17.6227L17.1713 11.3278L17.0811 11.166V10.4378L17.6227 10.0332Z" fill="white"/>
<path d="M20.241 17.8008H27.0119L27.4633 18.1244V18.6099L27.1021 18.9336H20.241L19.8799 18.6099V18.1244L20.241 17.8008Z" fill="white"/>
<path d="M17.713 17.5576H18.2547L18.7061 17.8813L18.7963 18.0431V18.6904L18.4352 19.0949L17.6227 19.1759L17.1713 18.8522L17.0811 18.6904V18.0431L17.5324 17.6385L17.713 17.5576Z" fill="white"/>
<path d="M20.1507 14.0791H27.1924L27.4633 14.3218V14.8882L27.1021 15.131H20.241L19.8799 14.8882V14.3218L20.1507 14.0791Z" fill="white"/>
<path d="M17.6227 13.8359H18.345L18.7963 14.3214V14.8878L18.4352 15.2924L18.2547 15.3733H17.5324L17.0811 14.8878V14.3214L17.4422 13.9168L17.6227 13.8359Z" fill="white"/>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.13101 0.157832C1.1659 0.499374 0.465985 1.21367 0.139314 2.19034C0.000267203 2.60603 0 2.63068 0 13.9311C0 25.2184 0.000467657 25.2567 0.138847 25.6719C0.480818 26.6979 1.25897 27.4433 2.33253 27.7729C2.57247 27.8467 4.02655 27.8634 10.1754 27.8634H17.7235L18.9679 28.7919C19.6523 29.3026 20.3175 29.7751 20.4461 29.8419C20.782 30.0164 21.6699 30.0553 22.0921 29.914C22.44 29.7975 25.3403 27.6908 26.2593 26.887C27.7013 25.6258 28.8449 23.9203 29.4272 22.1628C29.9056 20.7192 29.9984 19.8566 30 16.8422C30.0012 14.5318 29.9593 14.2058 29.5914 13.6632C29.1849 13.0638 28.5595 12.7254 27.8579 12.7254C27.3001 12.7254 26.04 12.5488 25.4756 12.3915C24.8257 12.2103 23.9366 11.8198 23.1225 11.3577L22.4507 10.9763V6.80589C22.4507 3.19655 22.4364 2.58647 22.3448 2.27166C22.0575 1.28514 21.2661 0.473519 20.2645 0.138005C19.8523 -0.000112619 19.7808 -0.00118429 11.2108 0.000222341L2.57247 0.00162896L2.13101 0.157832ZM19.4053 2.20635C19.7339 2.28278 19.9735 2.45948 20.1455 2.75212C20.2745 2.97162 20.2798 3.11845 20.2983 6.97729L20.3175 10.9751L19.864 11.2394C18.1975 12.2106 16.971 12.6017 15.3012 12.6942C14.2405 12.7529 13.9581 12.8542 13.4578 13.3558C12.7888 14.0264 12.7955 13.9866 12.7955 17.2802C12.7955 19.9074 12.8039 20.1013 12.9476 20.8121C13.2882 22.4964 14.0735 24.1943 15.0866 25.437L15.32 25.7233L9.14671 25.7049C3.05643 25.6868 2.97024 25.6846 2.73972 25.5489C2.41766 25.3592 2.22128 25.0405 2.17264 24.6282C2.15005 24.4366 2.14056 19.4873 2.15159 13.6297C2.1713 3.11845 2.17331 2.97658 2.3052 2.75212C2.47111 2.46979 2.72047 2.27909 3.015 2.20936C3.33299 2.13408 19.0821 2.13126 19.4053 2.20635ZM4.87768 4.33243C4.52775 4.46251 4.27632 4.89107 4.27632 5.3574C4.27632 5.73893 4.37775 5.97986 4.64288 6.22816L4.82162 6.39562H11.2253H17.6291L17.8078 6.22816C18.0729 5.97986 18.1744 5.73893 18.1744 5.3574C18.1744 4.97593 18.0729 4.73493 17.8079 4.48663L17.6291 4.31917L11.3035 4.30718C7.82439 4.30062 4.9328 4.312 4.87768 4.33243ZM4.87768 8.6193C4.52775 8.74938 4.27632 9.17793 4.27632 9.64426C4.27632 10.0258 4.37775 10.2667 4.64288 10.515L4.82162 10.6825H11.2253H17.6291L17.8078 10.515C18.0729 10.2667 18.1744 10.0258 18.1744 9.64426C18.1744 9.2628 18.0729 9.02179 17.8079 8.77349L17.6291 8.60603L11.3035 8.59404C7.82439 8.58748 4.9328 8.59887 4.87768 8.6193ZM4.87768 12.9056C4.52648 13.0383 4.27632 13.4649 4.27632 13.9311C4.27632 14.3127 4.37775 14.5536 4.64288 14.8019L4.82162 14.9693H7.48356H10.1455L10.3242 14.8019C10.5894 14.5536 10.6908 14.3127 10.6908 13.9311C10.6908 13.5497 10.5894 13.3087 10.3243 13.0604L10.1456 12.8929L7.56173 12.8803C6.14066 12.8733 4.9328 12.8847 4.87768 12.9056ZM21.8136 13.1929C23.475 14.2123 25.15 14.7409 27.254 14.9098L27.8146 14.9547L27.7749 17.3734C27.733 19.9256 27.7028 20.2181 27.3667 21.3225C27.2752 21.6234 27.0507 22.1641 26.8678 22.5241C26.1025 24.0311 25.364 24.8098 23.0648 26.5341L21.3729 27.8029L19.9574 26.7486C19.1788 26.1688 18.3086 25.4993 18.0235 25.2608C17.4774 24.8043 16.7066 23.925 16.3155 23.3127C15.9203 22.6941 15.4485 21.6311 15.2733 20.9643C15.0158 19.985 14.9671 19.3657 14.9671 17.0713V14.9498L15.5184 14.9061C16.846 14.8008 17.963 14.5611 19.009 14.1569C19.6201 13.9207 20.6723 13.3852 21.107 13.0891C21.2383 12.9996 21.3533 12.9264 21.3625 12.9264C21.3717 12.9264 21.5747 13.0464 21.8136 13.1929ZM4.87768 17.1924C4.52648 17.3252 4.27632 17.7518 4.27632 18.218C4.27632 18.5995 4.37775 18.8405 4.64288 19.0888L4.82162 19.2562H7.48356H10.1455L10.3242 19.0888C10.5894 18.8405 10.6908 18.5995 10.6908 18.218C10.6908 17.8365 10.5894 17.5956 10.3243 17.3472L10.1456 17.1798L7.56173 17.1672C6.14066 17.1602 4.9328 17.1716 4.87768 17.1924ZM4.87768 21.4794C4.52682 21.6115 4.27632 22.0387 4.27632 22.5049C4.27632 22.8864 4.37775 23.1273 4.64288 23.3756L4.82162 23.5431H8.0181H11.2146L11.3933 23.3756C11.6584 23.1273 11.7599 22.8864 11.7599 22.5049C11.7599 22.1234 11.6584 21.8825 11.3934 21.6341L11.2146 21.4666L8.09627 21.4542C6.3812 21.4473 4.9328 21.4587 4.87768 21.4794Z" fill="white"/>
<path d="M25.4688 17.5781L20.7422 22.3047L18.5938 20.1562" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
''';
static const String logout = '''
<svg viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5918 1.01172C3.52258 1.07119 3.43203 1.16624 3.35938 1.30371C3.24672 1.51694 3.20333 1.78932 3.28711 2.06348C3.36047 2.30347 3.50289 2.45196 3.58301 2.52246C3.62203 2.55679 3.65579 2.58072 3.66309 2.58594C3.67621 2.59534 3.6879 2.60313 3.69434 2.60742C3.70727 2.61604 3.71907 2.62322 3.72656 2.62793C3.74252 2.63796 3.76038 2.64893 3.77734 2.65918C3.81243 2.68039 3.86002 2.70817 3.91602 2.74121C4.02884 2.80779 4.18724 2.90119 4.37891 3.0127C4.76262 3.23591 5.28462 3.53759 5.85254 3.86426C7.3511 4.72623 8.13064 5.17554 8.56836 5.43945C8.99689 5.69784 9.0357 5.74158 9.08789 5.79004C9.26336 5.95292 9.43936 6.22514 9.51855 6.45801L9.57031 6.60938L9.58301 12.4355L9.5957 18.4258L9.59863 19.4297L9.62402 19.4287L9.60547 20.1768L9.58398 21.0322C9.57424 21.4245 9.56665 21.5995 9.55371 21.71C9.54562 21.779 9.53777 21.81 9.50195 21.9111C9.31858 22.4284 8.8897 22.8238 8.36719 22.9629C8.29661 22.9816 8.13612 23.0021 7.92676 22.998C7.71774 22.994 7.55448 22.9671 7.47949 22.9443C7.5142 22.9549 7.48529 22.9485 7.3291 22.8701C7.19453 22.8026 7.0054 22.7021 6.75098 22.5625C6.24329 22.284 5.50219 21.8634 4.46191 21.2646C3.14721 20.5079 2.42809 20.0915 2.00195 19.8291C1.68862 19.6361 1.56829 19.547 1.50195 19.4893L1.44824 19.4385C1.2473 19.2396 1.09782 18.968 1.04004 18.7344C1.03934 18.7213 1.03898 18.7038 1.03809 18.6816C1.03464 18.5957 1.03153 18.4691 1.02832 18.2969C1.02193 17.9535 1.01588 17.4485 1.01172 16.7646C1.00341 15.3977 1 13.3301 1 10.4443C1 6.78857 1.00155 4.73445 1.01172 3.5625C1.01682 2.97463 1.02399 2.62104 1.0332 2.40234C1.03859 2.27441 1.04466 2.21208 1.04688 2.18945C1.18692 1.67265 1.59937 1.2299 2.16406 1.04395C2.19857 1.04092 2.38794 1.02423 3.05176 1.01562C3.20763 1.0136 3.38635 1.01319 3.5918 1.01172Z" stroke="white" stroke-width="2"/>
<svg viewBox="0 0 23 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_10203_4005)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.23138 1.03298C2.46627 1.1245 1.88029 1.48382 1.41687 2.14568C0.948064 2.81544 0.913045 3.57089 0.95055 12.2164C0.984221 19.9991 0.984843 20.0187 1.20997 20.4407C1.56761 21.1115 2.08491 21.5002 4.40397 22.8403C8.19534 25.0314 8.13204 25.0011 8.90586 24.9962C10.5309 24.9862 11.4754 23.978 11.5797 22.1423L11.6318 21.2249L13.8194 21.1933C16.2797 21.1577 16.485 21.1071 17.2112 20.3558C17.8241 19.722 17.9235 19.2721 17.9235 17.1344C17.9235 15.3002 17.9202 15.2729 17.6692 15.021C17.3562 14.7067 16.8075 14.681 16.4587 14.9642C16.2222 15.1562 16.2122 15.2257 16.1572 17.0389C16.1078 18.671 16.0753 18.9427 15.908 19.1284C15.7265 19.3298 15.6012 19.3417 13.6629 19.3417H11.6101L11.581 13.2837L11.5519 7.22566L11.2671 6.73936C10.867 6.05618 10.5748 5.84516 7.83625 4.26166L5.4081 2.85766L10.4794 2.83072C13.784 2.81304 15.6195 2.84071 15.7483 2.90987C16.0665 3.08084 16.161 3.68238 16.1616 5.54034C16.1623 7.25592 16.1668 7.2917 16.4166 7.54234C16.7665 7.89365 17.3231 7.8898 17.678 7.5336L17.9409 7.26965L17.9063 5.0116C17.8812 3.3762 17.8337 2.68658 17.7336 2.51051C17.4798 2.0632 16.8541 1.44087 16.4283 1.21217C16.0102 0.987428 15.959 0.985556 9.94604 0.970268C6.61259 0.96174 3.59099 0.990028 3.23138 1.03298ZM3.0737 3.29103C3.03568 3.32909 3.0046 7.00112 3.0046 11.451C3.0046 19.3242 3.00998 19.5465 3.20559 19.7276C3.52386 20.0223 8.73564 22.9817 8.93642 22.9817C9.03588 22.9817 9.21045 22.8881 9.32441 22.7737C9.52779 22.5695 9.53162 22.427 9.53162 15.1458C9.53162 8.21064 9.51981 7.71529 9.35032 7.56283C9.25055 7.47318 8.02678 6.7379 6.63072 5.92899C5.23466 5.11997 3.96396 4.33477 3.80711 4.18397C3.65015 4.03317 3.46594 3.75497 3.39777 3.5658C3.27365 3.22124 3.20289 3.16124 3.0737 3.29103ZM18.4887 8.05298C18.0603 8.48292 18.1378 8.92284 18.7627 9.60924L19.2912 10.1897H16.061H12.831L12.5281 10.4937C12.1476 10.8756 12.13 11.304 12.4794 11.6825L12.7335 11.9577H16.0102H19.2869L18.7606 12.5505C18.1651 13.2212 18.0853 13.6115 18.4491 14.0756C18.6192 14.2928 18.7528 14.3497 19.0925 14.3497C19.5052 14.3497 19.5743 14.2963 20.9499 12.9156C22.8752 10.9828 22.8756 11.1559 20.9434 9.22891C19.6192 7.90832 19.4787 7.79766 19.1256 7.79766C18.8574 7.79766 18.6669 7.8741 18.4887 8.05298Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_10203_4005">
<rect width="23" height="26" fill="white"/>
</clipPath>
</defs>
</svg>
''';