pre and post bug fix
This commit is contained in:
parent
3ec22aa137
commit
a2d1480b5a
98
lib/presentation/claims_overview/claims_overview_cache.dart
Normal file
98
lib/presentation/claims_overview/claims_overview_cache.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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(),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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
@ -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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user