pre and post bug fix

This commit is contained in:
Surendiran 2026-07-03 11:45:37 +05:30
parent 3ec22aa137
commit a2d1480b5a
9 changed files with 1397 additions and 962 deletions

View File

@ -0,0 +1,98 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class ClaimsOverviewCacheEntry {
final String policyId;
final Map<String, dynamic> claimsKpiBySlug;
final Map<String, dynamic> enrollmentKpiBySlug;
final DateTime generatedAt;
final String? claimsLoadError;
final String? enrollmentLoadError;
const ClaimsOverviewCacheEntry({
required this.policyId,
required this.claimsKpiBySlug,
required this.enrollmentKpiBySlug,
required this.generatedAt,
this.claimsLoadError,
this.enrollmentLoadError,
});
factory ClaimsOverviewCacheEntry.fromJson(Map<String, dynamic> json) {
return ClaimsOverviewCacheEntry(
policyId: json['policyId']?.toString() ?? '',
claimsKpiBySlug: Map<String, dynamic>.from(json['claimsKpiBySlug'] ?? {}),
enrollmentKpiBySlug:
Map<String, dynamic>.from(json['enrollmentKpiBySlug'] ?? {}),
generatedAt: DateTime.tryParse(json['generatedAt']?.toString() ?? '') ??
DateTime.now(),
claimsLoadError: json['claimsLoadError']?.toString(),
enrollmentLoadError: json['enrollmentLoadError']?.toString(),
);
}
Map<String, dynamic> toJson() => {
'policyId': policyId,
'claimsKpiBySlug': claimsKpiBySlug,
'enrollmentKpiBySlug': enrollmentKpiBySlug,
'generatedAt': generatedAt.toIso8601String(),
'claimsLoadError': claimsLoadError,
'enrollmentLoadError': enrollmentLoadError,
};
}
abstract final class ClaimsOverviewCache {
static const _keyPrefix = 'claims_overview_cache_v1_';
static String _key(String branchId, String policyId) =>
'$_keyPrefix${branchId}_$policyId';
static Future<ClaimsOverviewCacheEntry?> read(
String branchId,
String policyId,
) async {
if (branchId.isEmpty || policyId.isEmpty) return null;
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_key(branchId, policyId));
if (raw == null || raw.isEmpty) return null;
try {
final decoded = jsonDecode(raw);
if (decoded is! Map) return null;
return ClaimsOverviewCacheEntry.fromJson(
Map<String, dynamic>.from(decoded),
);
} catch (_) {
return null;
}
}
static Future<void> write(
String branchId,
ClaimsOverviewCacheEntry entry,
) async {
if (branchId.isEmpty || entry.policyId.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
_key(branchId, entry.policyId),
jsonEncode(entry.toJson()),
);
}
static Future<void> clear(String branchId, String policyId) async {
if (branchId.isEmpty || policyId.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_key(branchId, policyId));
}
static Future<void> clearAll() async {
final prefs = await SharedPreferences.getInstance();
final keys = prefs
.getKeys()
.where((key) => key.startsWith(_keyPrefix))
.toList();
for (final key in keys) {
await prefs.remove(key);
}
}
}

View File

@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import '../../customAppBar/base_layout.dart'; import '../../customAppBar/base_layout.dart';
import '../../customAppBar/toastHelper.dart'; import '../../customAppBar/toastHelper.dart';
@ -10,6 +11,7 @@ import '../../service/api_service.dart';
import '../../service/secure_pop_scope.dart'; import '../../service/secure_pop_scope.dart';
import '../../service/token_storage_service.dart'; import '../../service/token_storage_service.dart';
import 'claims_collection_kpi.dart'; import 'claims_collection_kpi.dart';
import 'claims_overview_cache.dart';
import 'enrollment_collection_kpi.dart'; import 'enrollment_collection_kpi.dart';
import 'claims_overview_animations.dart'; import 'claims_overview_animations.dart';
import 'claims_overview_pdf_export.dart'; import 'claims_overview_pdf_export.dart';
@ -50,6 +52,13 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
String? _selectedPolicyId; String? _selectedPolicyId;
List<Map<String, dynamic>> _activePolicies = []; List<Map<String, dynamic>> _activePolicies = [];
Uint8List? _clientLogoBytes; Uint8List? _clientLogoBytes;
DateTime? _dataGeneratedAt;
String? get _formattedGeneratedAt {
final generatedAt = _dataGeneratedAt;
if (generatedAt == null) return null;
return DateFormat('d MMM yyyy, h:mm a').format(generatedAt.toLocal());
}
static const _tabs = [ static const _tabs = [
(Icons.dashboard_outlined, 'Overview'), (Icons.dashboard_outlined, 'Overview'),
@ -105,7 +114,10 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
}); });
} }
Future<void> _loadDashboard({bool reloadPolicies = false}) async { Future<void> _loadDashboard({
bool reloadPolicies = false,
bool forceRefresh = false,
}) async {
final isInitialLoad = _replayToken == 0; final isInitialLoad = _replayToken == 0;
setState(() { setState(() {
if (isInitialLoad) { if (isInitialLoad) {
@ -116,8 +128,8 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
}); });
try { try {
_clientId ??= await _tokenService.readValue('empClientId'); _clientId = await _tokenService.readValue('empClientId');
_clientBranchId ??= await _tokenService.readValue('empClientBranchId'); _clientBranchId = await _tokenService.readValue('empClientBranchId');
final hrId = await _tokenService.readValue('empHrId'); final hrId = await _tokenService.readValue('empHrId');
final token = await _tokenService.getCurrentToken(); final token = await _tokenService.getCurrentToken();
if (token != null && token.isNotEmpty) { if (token != null && token.isNotEmpty) {
@ -141,6 +153,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
error: 'No active policy found', error: 'No active policy found',
); );
_enrollmentViewData = EnrollmentOverviewViewData.empty(); _enrollmentViewData = EnrollmentOverviewViewData.empty();
_dataGeneratedAt = null;
_isLoading = false; _isLoading = false;
_isRefreshing = false; _isRefreshing = false;
}); });
@ -148,6 +161,28 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
} }
final policyId = _selectedPolicyId!; final policyId = _selectedPolicyId!;
final branchId = _clientBranchId ?? '';
if (forceRefresh) {
await ClaimsOverviewCache.clear(branchId, policyId);
}
if (!forceRefresh) {
final cached = await ClaimsOverviewCache.read(branchId, policyId);
if (cached != null) {
_applyCacheEntry(cached);
if (!mounted) return;
setState(() {
_isLoading = false;
_isRefreshing = false;
if (_replayToken == 0) {
_replayToken++;
}
});
return;
}
}
final response = await _apiService.getClaimsCollectionV2All( final response = await _apiService.getClaimsCollectionV2All(
clientPolicyId: policyId, clientPolicyId: policyId,
); );
@ -186,6 +221,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
setState(() { setState(() {
_viewData = claimsData; _viewData = claimsData;
_enrollmentViewData = enrollmentData; _enrollmentViewData = enrollmentData;
_dataGeneratedAt = DateTime.now();
_isLoading = false; _isLoading = false;
_isRefreshing = false; _isRefreshing = false;
_replayToken++; _replayToken++;
@ -195,10 +231,24 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
} }
} }
final generatedAt = DateTime.now();
await ClaimsOverviewCache.write(
branchId,
ClaimsOverviewCacheEntry(
policyId: policyId,
claimsKpiBySlug: claimsData.kpiBySlug,
enrollmentKpiBySlug: enrollmentData.kpiBySlug,
generatedAt: generatedAt,
claimsLoadError: claimsData.loadError,
enrollmentLoadError: enrollmentData.loadError,
),
);
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_viewData = claimsData; _viewData = claimsData;
_enrollmentViewData = enrollmentData; _enrollmentViewData = enrollmentData;
_dataGeneratedAt = generatedAt;
_isLoading = false; _isLoading = false;
_isRefreshing = false; _isRefreshing = false;
_replayToken++; _replayToken++;
@ -209,6 +259,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
_viewData = ClaimsOverviewViewData.empty(error: e.toString()); _viewData = ClaimsOverviewViewData.empty(error: e.toString());
_enrollmentViewData = _enrollmentViewData =
EnrollmentOverviewViewData.empty(error: e.toString()); EnrollmentOverviewViewData.empty(error: e.toString());
_dataGeneratedAt = null;
_isLoading = false; _isLoading = false;
_isRefreshing = false; _isRefreshing = false;
}); });
@ -216,9 +267,21 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
} }
} }
void _applyCacheEntry(ClaimsOverviewCacheEntry entry) {
_viewData = ClaimsOverviewViewData(
kpiBySlug: Map<String, dynamic>.from(entry.claimsKpiBySlug),
loadError: entry.claimsLoadError,
);
_enrollmentViewData = EnrollmentOverviewViewData(
kpiBySlug: Map<String, dynamic>.from(entry.enrollmentKpiBySlug),
loadError: entry.enrollmentLoadError,
);
_dataGeneratedAt = entry.generatedAt;
}
Future<void> _loadPolicyList(String token, String hrId) async { Future<void> _loadPolicyList(String token, String hrId) async {
_clientId ??= await _tokenService.readValue('empClientId'); _clientId = await _tokenService.readValue('empClientId');
_clientBranchId ??= await _tokenService.readValue('empClientBranchId'); _clientBranchId = await _tokenService.readValue('empClientBranchId');
if (_clientId == null || _clientBranchId == null) return; if (_clientId == null || _clientBranchId == null) return;
@ -386,7 +449,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
policyId: _selectedPolicyId ?? '', policyId: _selectedPolicyId ?? '',
dashboardInfo: ClaimsPdfDashboardInfo( dashboardInfo: ClaimsPdfDashboardInfo(
policyLabel: _selectedPolicyLabel(), policyLabel: _selectedPolicyLabel(),
misCreationDate: _viewData.liveUpdatedAt, misCreationDate: _formattedGeneratedAt,
), ),
clientLogoBytes: _clientLogoBytes, clientLogoBytes: _clientLogoBytes,
onProgress: (_, __, label) { onProgress: (_, __, label) {
@ -550,13 +613,17 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
color: ClaimsOverviewTheme.textSecondary, color: ClaimsOverviewTheme.textSecondary,
), ),
), ),
if (_viewData.liveUpdatedAt != null && if (_formattedGeneratedAt != null) ...[
_viewData.liveUpdatedAt!.isNotEmpty) ...[
const SizedBox(width: 12), const SizedBox(width: 12),
ClaimsLiveIndicator( ClaimsLiveIndicator(
createdAt: _viewData.liveUpdatedAt, createdAt: _formattedGeneratedAt,
), ),
], ],
ClaimsRefreshIcon(
isRefreshing: _isRefreshing,
onPressed: () =>
_loadDashboard(forceRefresh: true),
),
], ],
), ),
], ],
@ -593,11 +660,6 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
const SizedBox(width: 4),
ClaimsRefreshIcon(
isRefreshing: _isRefreshing,
onPressed: _loadDashboard,
),
], ],
), ),
), ),
@ -639,10 +701,6 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
Icons.arrow_drop_down, Icons.arrow_drop_down,
color: ClaimsOverviewTheme.textSecondary, color: ClaimsOverviewTheme.textSecondary,
), ),
ClaimsRefreshIcon(
isRefreshing: _isRefreshing,
onPressed: _loadDashboard,
),
], ],
), ),
), ),

View File

@ -41,7 +41,7 @@ class ClaimsTabPanelRow extends StatelessWidget {
panels.map((p) { panels.map((p) {
return SizedBox( return SizedBox(
width: available * p.flex / totalFlex, width: available * p.flex / totalFlex,
child: ClipRect(child: p.child), child: p.child,
); );
}).toList(), }).toList(),
), ),

View File

@ -5,6 +5,15 @@ import 'claims_overview_animations.dart';
import 'claims_overview_scope.dart'; import 'claims_overview_scope.dart';
import 'claims_overview_theme.dart'; import 'claims_overview_theme.dart';
bool _overviewUsesWideLayout(
BuildContext context,
BoxConstraints constraints, {
double minWidth = 520,
}) {
if (ClaimsPdfExportScope.of(context)) return true;
return constraints.maxWidth >= minWidth;
}
/// Hover / tap focus wrapper for dashboard tiles. /// Hover / tap focus wrapper for dashboard tiles.
class ClaimsFocusCard extends StatefulWidget { class ClaimsFocusCard extends StatefulWidget {
final Widget child; final Widget child;
@ -163,9 +172,9 @@ class ClaimsPolicyInformationBody extends StatelessWidget {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final narrow = constraints.maxWidth < 520; final wide = _overviewUsesWideLayout(context, constraints);
if (narrow) { if (!wide) {
return Column( return Column(
children: [ children: [
startTile, startTile,
@ -218,7 +227,7 @@ class ClaimsExperienceBody extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final wide = constraints.maxWidth >= 520; final wide = _overviewUsesWideLayout(context, constraints);
final incurredTile = _OverviewInnerTile( final incurredTile = _OverviewInnerTile(
label: 'Incurred Claims', label: 'Incurred Claims',
icon: Icons.gps_fixed, icon: Icons.gps_fixed,
@ -283,7 +292,11 @@ class ClaimsInceptionBody extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final wide = constraints.maxWidth >= 400; final wide = _overviewUsesWideLayout(
context,
constraints,
minWidth: 400,
);
final valueStyle = GoogleFonts.poppins( final valueStyle = GoogleFonts.poppins(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@ -519,7 +532,7 @@ class ClaimsPremiumMembershipBody extends StatelessWidget {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (constraints.maxWidth < 520) { if (!_overviewUsesWideLayout(context, constraints)) {
return Column( return Column(
children: [ children: [
premiumTile, premiumTile,

File diff suppressed because it is too large Load Diff

View File

@ -566,7 +566,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
case 'emp_count': case 'emp_count':
return relationship == 'self'; return relationship == 'self';
case 'enrolled': case 'enrolled':
return _isEnrolledStatus(status); return _isSelfRow(row) && _isEnrolledStatus(status);
case 'not_enrolled': case 'not_enrolled':
return !_isEnrolledStatus(status); return !_isEnrolledStatus(status);
case 'logged_in': case 'logged_in':
@ -574,7 +574,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
case 'not_logged_in': case 'not_logged_in':
return _isSelfRow(row) && !_isLoggedInRow(row); return _isSelfRow(row) && !_isLoggedInRow(row);
case 'draft': case 'draft':
return status == 'draft'; return _isSelfRow(row) && status == 'draft';
default: default:
return true; return true;
} }
@ -1934,9 +1934,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
item['relationship']?.toString().toLowerCase().trim() ?? ''; item['relationship']?.toString().toLowerCase().trim() ?? '';
if (relationship == 'self') empCount++; if (relationship == 'self') empCount++;
if (_isEnrolledStatus(status)) { if (_isSelfRow(item) && _isEnrolledStatus(status)) {
enrolled++; enrolled++;
} else { } else if (!_isEnrolledStatus(status)) {
notEnrolled++; notEnrolled++;
} }
if (_isSelfRow(item)) { if (_isSelfRow(item)) {
@ -1946,7 +1946,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
notLoggedIn++; notLoggedIn++;
} }
} }
if (status == 'draft') draft++; if (status == 'draft' && _isSelfRow(item)) draft++;
} }
return { return {

View File

@ -900,125 +900,138 @@ class _postFileUploadState extends State<postFileUpload> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( LayoutBuilder(
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, constraints) {
children: [ final narrow = constraints.maxWidth < 640;
/// Select File Action final fileActionDropdown = buildStyledDropdown(
Expanded( label: 'Select File Action',
flex: 5, value: selectedKey,
child: buildStyledDropdown( items: getFileUploadMasterList,
label: 'Select File Action', onChanged: (val) {
value: selectedKey, setState(() {
items: getFileUploadMasterList, selectedKey = val;
onChanged: (val) { final selectedItem =
setState(() { getFileUploadMasterList.firstWhere(
selectedKey = val; (e) => e['key'] == val,
final selectedItem = getFileUploadMasterList );
.firstWhere((e) => e['key'] == val); selectedValue = selectedItem['value'];
selectedValue = selectedItem['value']; currentApiValue = selectedItem['key'];
currentApiValue = selectedItem['key']; showSampleButton = true;
showSampleButton = true; });
}); },
}, );
), final uploadField = Column(
), crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 16), RichText(
text: TextSpan(
/// Upload Box text: 'Upload File',
Expanded( style: GoogleFonts.poppins(
flex: 5, fontSize: 12,
child: Column( fontWeight: FontWeight.w500,
crossAxisAlignment: CrossAxisAlignment.start, color: Colors.black,
children: [ ),
/// LABEL children: const [
RichText( TextSpan(
text: TextSpan( text: '(Supported Formats: XLSX)',
text: 'Upload File', style: TextStyle(
style: GoogleFonts.poppins( fontSize: 11,
fontSize: 12, color: Colors.grey,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w400,
color: Colors.black, ),
), ),
children: const [ ],
TextSpan( ),
text: '(Supported Formats: XLSX)', ),
style: TextStyle( const SizedBox(height: 6),
fontSize: 11, DragTarget<html.File>(
color: Colors.grey, onAccept: (html.File droppedFile) {
fontWeight: FontWeight.w400, setState(() {
fileName = droppedFile.name;
});
_dragAndDropFile(droppedFile);
},
builder:
(context, candidateData, rejectedData) {
return GestureDetector(
onTap: () {
if (selectedValue != null) {
_uploadFile();
} else {
ToastHelper.showErrorToast(
context,
'Please select file action',
);
}
},
child: Container(
height: 40,
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFF00A6A6),
width: 1,
), ),
), ),
], child: Row(
), children: [
), Expanded(
child: Text(
const SizedBox(height: 6), fileName ??
'Upload Your Documents',
/// DOTTED UPLOAD BOX overflow: TextOverflow.ellipsis,
DragTarget<html.File>( style: GoogleFonts.poppins(
onAccept: (html.File droppedFile) { fontSize: 13,
setState(() { color: fileName == null
fileName = droppedFile.name; ? Colors.grey
}); : Colors.black,
_dragAndDropFile(droppedFile); ),
},
builder:
(context, candidateData, rejectedData) {
return GestureDetector(
onTap: () {
if (selectedValue != null) {
_uploadFile();
} else {
ToastHelper.showErrorToast(
context,
'Please select file action',
);
}
},
child: Container(
height: 40,
padding: const EdgeInsets.symmetric(
horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFF00A6A6),
width: 1,
), ),
), ),
child: Row( const Icon(
children: [ Icons.file_upload_outlined,
Expanded( size: 18,
child: Text( color: Colors.black,
fileName ??
'Upload Your Documents',
overflow:
TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
color: fileName == null
? Colors.grey
: Colors.black,
),
),
),
const Icon(
Icons.file_upload_outlined,
size: 18,
color: Colors.black,
),
],
), ),
)); ],
}, ),
), ),
);
},
),
],
);
if (narrow) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
fileActionDropdown,
const SizedBox(height: 16),
uploadField,
], ],
), );
), }
],
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 5,
child: fileActionDropdown,
),
const SizedBox(width: 16),
Expanded(
flex: 5,
child: uploadField,
),
],
);
},
), ),
SizedBox(height: 20), SizedBox(height: 20),
Column( Column(
@ -1146,27 +1159,35 @@ class _postFileUploadState extends State<postFileUpload> {
); );
} }
return GridView.builder( return LayoutBuilder(
shrinkWrap: true, // IMPORTANT builder: (context, constraints) {
physics: const NeverScrollableScrollPhysics(), // Disable inner scroll final width = constraints.maxWidth;
padding: const EdgeInsets.all(16), final crossAxisCount = width < 640 ? 1 : 2;
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( const mainAxisExtent = 88.0;
crossAxisCount: 2,
crossAxisSpacing: 16, return GridView.builder(
mainAxisSpacing: 16, shrinkWrap: true,
childAspectRatio: 10, physics: const NeverScrollableScrollPhysics(),
), padding: const EdgeInsets.all(16),
itemCount: _paginatedData.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
itemBuilder: (context, index) { crossAxisCount: crossAxisCount,
final item = _paginatedData[index]; crossAxisSpacing: 16,
return _buildFileCard(item); mainAxisSpacing: 16,
mainAxisExtent: mainAxisExtent,
),
itemCount: _paginatedData.length,
itemBuilder: (context, index) {
final item = _paginatedData[index];
return _buildFileCard(item);
},
);
}, },
); );
} }
Widget _buildFileCard(Map<String, dynamic> item) { Widget _buildFileCard(Map<String, dynamic> item) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEFF9FA), color: const Color(0xFFEFF9FA),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -1175,10 +1196,9 @@ class _postFileUploadState extends State<postFileUpload> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
/// 📄 File Icon
Container( Container(
height: 44, height: 40,
width: 44, width: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -1187,18 +1207,16 @@ class _postFileUploadState extends State<postFileUpload> {
child: const Icon( child: const Icon(
Icons.description_outlined, Icons.description_outlined,
color: Color(0xFF00A6A6), color: Color(0xFF00A6A6),
size: 22, size: 20,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
/// 📑 LEFT CONTENT
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
/// Row 1 File name
Text( Text(
item['file_name'] ?? '-', item['file_name'] ?? '-',
maxLines: 1, maxLines: 1,
@ -1209,12 +1227,9 @@ class _postFileUploadState extends State<postFileUpload> {
color: const Color(0xFF101010), color: const Color(0xFF101010),
), ),
), ),
const SizedBox(height: 2),
const SizedBox(height: 4), Text.rich(
TextSpan(
/// Row 2 Action - Date
RichText(
text: TextSpan(
style: GoogleFonts.poppins(fontSize: 11), style: GoogleFonts.poppins(fontSize: 11),
children: [ children: [
TextSpan( TextSpan(
@ -1234,106 +1249,82 @@ class _postFileUploadState extends State<postFileUpload> {
), ),
], ],
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
], ],
), ),
), ),
const SizedBox(width: 8),
/// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD) Row(
Column( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
/// 🔴 Error + Status if (item['file_error_status'] == '1')
Row( Tooltip(
children: [], message: 'Info',
), child: InkWell(
onTap: () async {
final String? token =
await tokenService.getCurrentToken();
final String? empClientId =
await tokenService.readValue('empClientId');
final String? empBranchId =
await tokenService.readValue('empClientBranchId');
const SizedBox(height: 8), if (token == null ||
empClientId == null ||
empBranchId == null) {
debugPrint(
'❌ Missing required data for navigation $token');
return;
}
Row( if (!context.mounted) return;
children: [ await openExcelErrorScreenIfAvailable(
if (item['file_error_status'] == '1') context: context,
InkWell( apiService: apiService,
onTap: () async { fileId: item['id'].toString(),
logDebug(item); tokenType: 'post',
// return; clientId: empClientId,
final String? token = policyNo: item['policy_no']?.toString() ?? '',
await tokenService.getCurrentToken(); action: item['file_action']?.toString() ?? '',
final String? empClientId = createdAt: item['created_at']?.toString() ?? '',
await tokenService.readValue('empClientId'); clientBranchId: empBranchId,
final String? empBranchId = token: token,
await tokenService.readValue('empClientBranchId'); );
logDebug(item);
logDebug(empClientId);
logDebug(localPolicyTypeId);
logDebug(empBranchId);
logDebug(token);
logDebug('post');
logDebug(localCardType);
logDebug(localCardPolicyNo);
logDebug(localCardInsurerName);
logDebug(localCardPolicyName);
logDebug(localCardPolicyExpDate);
logDebug(item['id']);
// SAFETY CHECK
if (token == null ||
empClientId == null ||
empBranchId == null) {
debugPrint(
'❌ Missing required data for navigation ${token}');
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => excelErrorScreen(
ClientId: empClientId,
policy_no: item['policy_no'],
action: item['file_action'],
created_at: item['created_at'],
clientBranchId: empBranchId,
Token: token,
TokenType: 'post',
id: item['id']),
),
);
},
child: Icon(
Icons.error,
size: 16,
color: Colors.red,
),
),
SizedBox(width: 10),
_buildStatusChip(item['status']),
SizedBox(width: 10),
InkWell(
onTap: () {
getHrFileDownload(item['id'], item['file_name']);
}, },
child: Container( child: const Icon(
height: 30, Icons.error,
width: 30, size: 16,
decoration: BoxDecoration( color: Colors.red,
color: Color(0xFFC5F2F4),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF76CED2)),
),
child: Icon(
Icons.file_download_outlined,
color: Color(0xFF1D1B20),
size: 22,
),
), ),
), ),
], ),
const SizedBox(width: 8),
_buildStatusChip(item['status']),
const SizedBox(width: 8),
Tooltip(
message: 'Download',
child: InkWell(
onTap: () {
getHrFileDownload(item['id'], item['file_name']);
},
child: Container(
height: 28,
width: 28,
decoration: BoxDecoration(
color: Color(0xFFC5F2F4),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF76CED2)),
),
child: const Icon(
Icons.file_download_outlined,
color: Color(0xFF1D1B20),
size: 20,
),
),
),
), ),
/// Download
], ],
), ),
], ],
@ -1428,75 +1419,109 @@ class _postFileUploadState extends State<postFileUpload> {
List<int> visiblePages = getVisiblePages(); List<int> visiblePages = getVisiblePages();
return Row( Widget paginationControls = Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( DropdownButton<int>(
padding: const EdgeInsets.symmetric(vertical: 12), value: _rowsPerPage,
child: Row( items: [6, 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(
tooltip: 'Previous Page',
onPressed:
_currentPage > 1 ? () => setState(() => _currentPage--) : null,
icon: const Icon(Icons.chevron_left),
),
if (!visiblePages.contains(1))
Row(
children: [ children: [
// Dropdown for rows per page _buildPageButton(1),
DropdownButton<int>( const Padding(
value: _rowsPerPage, padding: EdgeInsets.symmetric(horizontal: 4),
items: [6, 10, 15, 20, 50].map((int value) { child: Text('...'),
return DropdownMenuItem<int>(
value: value,
child: Text(' $value ',
style: GoogleFonts.poppins(fontSize: 15)),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_rowsPerPage = newValue!;
_currentPage = 1;
});
},
),
// Previous button
IconButton(
tooltip: 'Previous Page',
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),
), ),
], ],
), ),
for (int page in visiblePages) _buildPageButton(page),
if (!visiblePages.contains(totalPages) && totalPages > 0)
Row(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text('...'),
),
_buildPageButton(totalPages),
],
),
IconButton(
onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++)
: null,
icon: const Icon(Icons.chevron_right),
), ),
], ],
); );
final showingText = Text(
'Showing $startEntry to $endEntry of $totalItems entries',
style: GoogleFonts.poppins(
fontSize: 13,
color: const Color(0xFF585757),
fontWeight: FontWeight.w400,
),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: LayoutBuilder(
builder: (context, constraints) {
final narrow = constraints.maxWidth < 720;
if (narrow) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
showingText,
const SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: paginationControls,
),
),
],
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
showingText,
Flexible(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: paginationControls,
),
),
],
);
},
),
);
} }
Widget _buildPageButton(int page) { Widget _buildPageButton(int page) {

View File

@ -930,66 +930,140 @@ class _excelVerifyState extends State<preFileUpload> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( LayoutBuilder(
children: [ builder: (context, constraints) {
SizedBox( final narrow = constraints.maxWidth < 580;
width: 260, // 👈 set your required width if (narrow) {
child: _dateField( return Column(
label: 'Enrolment Open Date', crossAxisAlignment: CrossAxisAlignment.start,
controller: openDateController, children: [
onTap: () async { _dateField(
final picked = await showDatePicker( label: 'Enrolment Open Date',
context: context, controller: openDateController,
firstDate: DateTime(2000), onTap: () async {
lastDate: DateTime.now(), final picked = await showDatePicker(
initialDate: DateTime.now(), context: context,
); firstDate: DateTime(2000),
if (picked != null) { lastDate: DateTime.now(),
final formatted = initialDate: DateTime.now(),
DateFormat('dd-MM-yyyy').format(picked); );
if (picked != null) {
final formatted =
DateFormat('dd-MM-yyyy')
.format(picked);
// If open date changed, clear close date if (openDateController.text !=
if (openDateController.text != formatted) { formatted) {
closeDateController.clear(); closeDateController.clear();
} }
openDateController.text = formatted; openDateController.text = formatted;
} }
}, },
), ),
), const SizedBox(height: 16),
const SizedBox(width: 16), _dateField(
SizedBox( label: 'Enrolment Close Date',
width: 260, // 👈 same width controller: closeDateController,
child: _dateField( onTap: () async {
label: 'Enrolment Close Date', if (openDateController.text.isEmpty) {
controller: closeDateController, ToastHelper.showErrorToast(
onTap: () async { context,
if (openDateController.text.isEmpty) { 'Please select Enrolment Open Date first',
ToastHelper.showErrorToast(context, );
'Please select Enrolment Open Date first'); return;
return; }
}
final openDate = DateFormat('dd-MM-yyyy') final openDate =
.parse(openDateController.text); DateFormat('dd-MM-yyyy').parse(
openDateController.text,
);
final picked = await showDatePicker( final picked = await showDatePicker(
context: context, context: context,
firstDate: firstDate: openDate,
openDate, // Cannot select before open date lastDate: DateTime(2100),
lastDate: DateTime(2100), initialDate: openDate,
initialDate: openDate, );
);
if (picked != null) { if (picked != null) {
closeDateController.text = closeDateController.text =
DateFormat('dd-MM-yyyy').format(picked); DateFormat('dd-MM-yyyy')
} .format(picked);
}, }
), },
), ),
], ],
);
}
return Row(
children: [
SizedBox(
width: 260,
child: _dateField(
label: 'Enrolment Open Date',
controller: openDateController,
onTap: () async {
final picked = await showDatePicker(
context: context,
firstDate: DateTime(2000),
lastDate: DateTime.now(),
initialDate: DateTime.now(),
);
if (picked != null) {
final formatted =
DateFormat('dd-MM-yyyy')
.format(picked);
if (openDateController.text !=
formatted) {
closeDateController.clear();
}
openDateController.text = formatted;
}
},
),
),
const SizedBox(width: 16),
SizedBox(
width: 260,
child: _dateField(
label: 'Enrolment Close Date',
controller: closeDateController,
onTap: () async {
if (openDateController.text.isEmpty) {
ToastHelper.showErrorToast(
context,
'Please select Enrolment Open Date first',
);
return;
}
final openDate =
DateFormat('dd-MM-yyyy').parse(
openDateController.text,
);
final picked = await showDatePicker(
context: context,
firstDate: openDate,
lastDate: DateTime(2100),
initialDate: openDate,
);
if (picked != null) {
closeDateController.text =
DateFormat('dd-MM-yyyy')
.format(picked);
}
},
),
),
],
);
},
), ),
SizedBox(height: 20), SizedBox(height: 20),
Row( Row(
@ -1288,20 +1362,28 @@ class _excelVerifyState extends State<preFileUpload> {
return const Center(child: Text('No uploaded files')); return const Center(child: Text('No uploaded files'));
} }
return GridView.builder( return LayoutBuilder(
shrinkWrap: true, // IMPORTANT builder: (context, constraints) {
physics: const NeverScrollableScrollPhysics(), // Disable inner scroll final width = constraints.maxWidth;
padding: const EdgeInsets.all(16), final crossAxisCount = width < 640 ? 1 : 2;
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( const mainAxisExtent = 88.0;
crossAxisCount: 2,
crossAxisSpacing: 16, return GridView.builder(
mainAxisSpacing: 16, shrinkWrap: true,
childAspectRatio: 10, physics: const NeverScrollableScrollPhysics(),
), padding: const EdgeInsets.all(16),
itemCount: _paginatedData.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
itemBuilder: (context, index) { crossAxisCount: crossAxisCount,
final item = _paginatedData[index]; crossAxisSpacing: 16,
return _buildFileCard(item); mainAxisSpacing: 16,
mainAxisExtent: mainAxisExtent,
),
itemCount: _paginatedData.length,
itemBuilder: (context, index) {
final item = _paginatedData[index];
return _buildFileCard(item);
},
);
}, },
); );
} }
@ -1333,7 +1415,7 @@ class _excelVerifyState extends State<preFileUpload> {
Widget _buildFileCard(Map<String, dynamic> item) { Widget _buildFileCard(Map<String, dynamic> item) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEFF9FA), color: const Color(0xFFEFF9FA),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -1342,10 +1424,9 @@ class _excelVerifyState extends State<preFileUpload> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
/// 📄 File Icon
Container( Container(
height: 44, height: 40,
width: 44, width: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -1354,18 +1435,16 @@ class _excelVerifyState extends State<preFileUpload> {
child: const Icon( child: const Icon(
Icons.description_outlined, Icons.description_outlined,
color: Color(0xFF00A6A6), color: Color(0xFF00A6A6),
size: 22, size: 20,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
/// 📑 LEFT CONTENT
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
/// Row 1 File name
Text( Text(
item['file_name'] ?? '-', item['file_name'] ?? '-',
maxLines: 1, maxLines: 1,
@ -1376,12 +1455,9 @@ class _excelVerifyState extends State<preFileUpload> {
color: const Color(0xFF101010), color: const Color(0xFF101010),
), ),
), ),
const SizedBox(height: 2),
const SizedBox(height: 4), Text.rich(
TextSpan(
/// Row 2 Action - Date
RichText(
text: TextSpan(
style: GoogleFonts.poppins(fontSize: 11), style: GoogleFonts.poppins(fontSize: 11),
children: [ children: [
TextSpan( TextSpan(
@ -1401,113 +1477,83 @@ class _excelVerifyState extends State<preFileUpload> {
), ),
], ],
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
], ],
), ),
), ),
const SizedBox(width: 8),
/// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD) Row(
Column( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
/// 🔴 Error + Status if (item['file_error_status'] == '1')
Row( Tooltip(
children: [], message: 'Info',
), child: InkWell(
onTap: () async {
final String? token =
await tokenService.getCurrentToken();
final String? enrollmentClient_id = await tokenService
.readValue('enrollmentClient_id');
final String? enrollmentEmpClientBranchId =
await tokenService
.readValue('enrollmentEmpClientBranchId');
const SizedBox(height: 8), if (token == null ||
enrollmentClient_id == null ||
enrollmentEmpClientBranchId == null) {
debugPrint(
'❌ Missing required data for navigation $token');
return;
}
Row( if (!context.mounted) return;
children: [ await openExcelErrorScreenIfAvailable(
if (item['file_error_status'] == '1') context: context,
Tooltip( apiService: apiService,
message: 'Info', // Added tooltip name fileId: item['id'].toString(),
child: InkWell( tokenType: 'pre',
onTap: () async { clientId: enrollmentClient_id,
logDebug(item); policyNo: item['policy_no']?.toString() ?? '',
// return; action: item['file_action']?.toString() ?? '',
final String? token = createdAt: item['created_at']?.toString() ?? '',
await tokenService.getCurrentToken(); clientBranchId: enrollmentEmpClientBranchId,
final String? enrollmentClient_id = await tokenService token: token,
.readValue('enrollmentClient_id'); );
final String? enrollmentEmpClientBranchId = },
await tokenService child: const Icon(
.readValue('enrollmentEmpClientBranchId'); Icons.error,
size: 16,
logDebug(item); color: Colors.red,
logDebug(enrollmentClient_id);
logDebug(localPolicyTypeId);
logDebug(enrollmentEmpClientBranchId);
logDebug(token);
logDebug('post');
logDebug(localCardType);
logDebug(localCardPolicyNo);
logDebug(localCardInsurerName);
logDebug(localCardPolicyName);
logDebug(localCardPolicyExpDate);
logDebug(item['id']);
// SAFETY CHECK
if (token == null ||
enrollmentClient_id == null ||
enrollmentEmpClientBranchId == null) {
debugPrint(
'❌ Missing required data for navigation ${token}');
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => excelErrorScreen(
ClientId: enrollmentClient_id,
policy_no: item['policy_no'],
action: item['file_action'],
created_at: item['created_at'],
clientBranchId: enrollmentEmpClientBranchId,
Token: token,
TokenType: 'pre',
id: item['id']),
),
);
},
child: Icon(
Icons.error,
size: 16,
color: Colors.red,
),
),
),
SizedBox(width: 10),
_buildStatusChip(item['status']),
SizedBox(width: 10),
Tooltip(
message: 'Download', // Added tooltip name
child: InkWell(
onTap: () {
getHrFileDownload(item['id'], item['file_name']);
},
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
color: Color(0xFFC5F2F4),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF76CED2)),
),
child: Icon(
Icons.file_download_outlined,
color: Color(0xFF1D1B20),
size: 22,
),
),
), ),
), ),
], ),
const SizedBox(width: 8),
_buildStatusChip(item['status']),
const SizedBox(width: 8),
Tooltip(
message: 'Download',
child: InkWell(
onTap: () {
getHrFileDownload(item['id'], item['file_name']);
},
child: Container(
height: 28,
width: 28,
decoration: BoxDecoration(
color: Color(0xFFC5F2F4),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF76CED2)),
),
child: const Icon(
Icons.file_download_outlined,
color: Color(0xFF1D1B20),
size: 20,
),
),
),
), ),
/// Download
], ],
), ),
], ],
@ -1586,86 +1632,106 @@ class _excelVerifyState extends State<preFileUpload> {
List<int> visiblePages = getVisiblePages(); List<int> visiblePages = getVisiblePages();
return Padding( Widget paginationControls = Row(
// Match this horizontal padding (16) to your Table Header padding for perfect alignment mainAxisSize: MainAxisSize.min,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), children: [
child: Row( DropdownButton<int>(
mainAxisAlignment: MainAxisAlignment value: _rowsPerPage,
.spaceBetween, // Pushes text to left, buttons to right items: [6, 10, 15, 20, 50].map((int value) {
children: [ return DropdownMenuItem<int>(
// --- LEFT SIDE: Showing Text --- value: value,
Text( child: Text(
"Showing $startEntry to $endEntry of $totalItems entries", ' $value ',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 15),
fontSize: 13, ),
color: const Color(0xFF585757), );
fontWeight: FontWeight.w400, }).toList(),
), onChanged: (newValue) {
), setState(() {
_rowsPerPage = newValue!;
// --- RIGHT SIDE: Controls --- _currentPage = 1;
});
},
),
IconButton(
onPressed:
_currentPage > 1 ? () => setState(() => _currentPage--) : null,
icon: const Icon(Icons.chevron_left),
),
if (!visiblePages.contains(1))
Row( Row(
children: [ children: [
// Dropdown for rows per page _buildPageButton(1),
DropdownButton<int>( const Padding(
value: _rowsPerPage, padding: EdgeInsets.symmetric(horizontal: 4),
// focusColor: Colors.transparent, // Fix: Removes the grey/blue highlight on change child: Text('...'),
items: [6, 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) && totalPages > 0)
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),
), ),
], ],
), ),
], for (int page in visiblePages) _buildPageButton(page),
if (!visiblePages.contains(totalPages) && totalPages > 0)
Row(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text('...'),
),
_buildPageButton(totalPages),
],
),
IconButton(
onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++)
: null,
icon: const Icon(Icons.chevron_right),
),
],
);
final showingText = Text(
'Showing $startEntry to $endEntry of $totalItems entries',
style: GoogleFonts.poppins(
fontSize: 13,
color: const Color(0xFF585757),
fontWeight: FontWeight.w400,
),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: LayoutBuilder(
builder: (context, constraints) {
final narrow = constraints.maxWidth < 720;
if (narrow) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
showingText,
const SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: paginationControls,
),
),
],
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
showingText,
Flexible(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: paginationControls,
),
),
],
);
},
), ),
); );
} }

View File

@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../presentation/claims_overview/claims_overview_cache.dart';
import 'package:nhancepolicy/logger.dart'; import 'package:nhancepolicy/logger.dart';
class TokenStorageService { class TokenStorageService {
@ -268,7 +270,10 @@ class TokenStorageService {
// 1 Clear ONLY branch/session related keys // 1 Clear ONLY branch/session related keys
await clearBranchSession(); await clearBranchSession();
// 2 Save selected branch // 2 Clear claims overview cache so the dashboard fetches fresh data
await ClaimsOverviewCache.clearAll();
// 3 Save selected branch
await saveSelectedBranch(newBranch); await saveSelectedBranch(newBranch);
// 3 Rebuild decoded session data from token // 3 Rebuild decoded session data from token