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:google_fonts/google_fonts.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../customAppBar/base_layout.dart';
|
||||
import '../../customAppBar/toastHelper.dart';
|
||||
@ -10,6 +11,7 @@ import '../../service/api_service.dart';
|
||||
import '../../service/secure_pop_scope.dart';
|
||||
import '../../service/token_storage_service.dart';
|
||||
import 'claims_collection_kpi.dart';
|
||||
import 'claims_overview_cache.dart';
|
||||
import 'enrollment_collection_kpi.dart';
|
||||
import 'claims_overview_animations.dart';
|
||||
import 'claims_overview_pdf_export.dart';
|
||||
@ -50,6 +52,13 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
String? _selectedPolicyId;
|
||||
List<Map<String, dynamic>> _activePolicies = [];
|
||||
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 = [
|
||||
(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;
|
||||
setState(() {
|
||||
if (isInitialLoad) {
|
||||
@ -116,8 +128,8 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
});
|
||||
|
||||
try {
|
||||
_clientId ??= await _tokenService.readValue('empClientId');
|
||||
_clientBranchId ??= await _tokenService.readValue('empClientBranchId');
|
||||
_clientId = await _tokenService.readValue('empClientId');
|
||||
_clientBranchId = await _tokenService.readValue('empClientBranchId');
|
||||
final hrId = await _tokenService.readValue('empHrId');
|
||||
final token = await _tokenService.getCurrentToken();
|
||||
if (token != null && token.isNotEmpty) {
|
||||
@ -141,6 +153,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
error: 'No active policy found',
|
||||
);
|
||||
_enrollmentViewData = EnrollmentOverviewViewData.empty();
|
||||
_dataGeneratedAt = null;
|
||||
_isLoading = false;
|
||||
_isRefreshing = false;
|
||||
});
|
||||
@ -148,6 +161,28 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
}
|
||||
|
||||
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(
|
||||
clientPolicyId: policyId,
|
||||
);
|
||||
@ -186,6 +221,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
setState(() {
|
||||
_viewData = claimsData;
|
||||
_enrollmentViewData = enrollmentData;
|
||||
_dataGeneratedAt = DateTime.now();
|
||||
_isLoading = false;
|
||||
_isRefreshing = false;
|
||||
_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;
|
||||
setState(() {
|
||||
_viewData = claimsData;
|
||||
_enrollmentViewData = enrollmentData;
|
||||
_dataGeneratedAt = generatedAt;
|
||||
_isLoading = false;
|
||||
_isRefreshing = false;
|
||||
_replayToken++;
|
||||
@ -209,6 +259,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
_viewData = ClaimsOverviewViewData.empty(error: e.toString());
|
||||
_enrollmentViewData =
|
||||
EnrollmentOverviewViewData.empty(error: e.toString());
|
||||
_dataGeneratedAt = null;
|
||||
_isLoading = 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 {
|
||||
_clientId ??= await _tokenService.readValue('empClientId');
|
||||
_clientBranchId ??= await _tokenService.readValue('empClientBranchId');
|
||||
_clientId = await _tokenService.readValue('empClientId');
|
||||
_clientBranchId = await _tokenService.readValue('empClientBranchId');
|
||||
|
||||
if (_clientId == null || _clientBranchId == null) return;
|
||||
|
||||
@ -386,7 +449,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
policyId: _selectedPolicyId ?? '',
|
||||
dashboardInfo: ClaimsPdfDashboardInfo(
|
||||
policyLabel: _selectedPolicyLabel(),
|
||||
misCreationDate: _viewData.liveUpdatedAt,
|
||||
misCreationDate: _formattedGeneratedAt,
|
||||
),
|
||||
clientLogoBytes: _clientLogoBytes,
|
||||
onProgress: (_, __, label) {
|
||||
@ -550,13 +613,17 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
color: ClaimsOverviewTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
if (_viewData.liveUpdatedAt != null &&
|
||||
_viewData.liveUpdatedAt!.isNotEmpty) ...[
|
||||
if (_formattedGeneratedAt != null) ...[
|
||||
const SizedBox(width: 12),
|
||||
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,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
ClaimsRefreshIcon(
|
||||
isRefreshing: _isRefreshing,
|
||||
onPressed: _loadDashboard,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -639,10 +701,6 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
||||
Icons.arrow_drop_down,
|
||||
color: ClaimsOverviewTheme.textSecondary,
|
||||
),
|
||||
ClaimsRefreshIcon(
|
||||
isRefreshing: _isRefreshing,
|
||||
onPressed: _loadDashboard,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -41,7 +41,7 @@ class ClaimsTabPanelRow extends StatelessWidget {
|
||||
panels.map((p) {
|
||||
return SizedBox(
|
||||
width: available * p.flex / totalFlex,
|
||||
child: ClipRect(child: p.child),
|
||||
child: p.child,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
@ -5,6 +5,15 @@ import 'claims_overview_animations.dart';
|
||||
import 'claims_overview_scope.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.
|
||||
class ClaimsFocusCard extends StatefulWidget {
|
||||
final Widget child;
|
||||
@ -163,9 +172,9 @@ class ClaimsPolicyInformationBody extends StatelessWidget {
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final narrow = constraints.maxWidth < 520;
|
||||
final wide = _overviewUsesWideLayout(context, constraints);
|
||||
|
||||
if (narrow) {
|
||||
if (!wide) {
|
||||
return Column(
|
||||
children: [
|
||||
startTile,
|
||||
@ -218,7 +227,7 @@ class ClaimsExperienceBody extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final wide = constraints.maxWidth >= 520;
|
||||
final wide = _overviewUsesWideLayout(context, constraints);
|
||||
final incurredTile = _OverviewInnerTile(
|
||||
label: 'Incurred Claims',
|
||||
icon: Icons.gps_fixed,
|
||||
@ -283,7 +292,11 @@ class ClaimsInceptionBody extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final wide = constraints.maxWidth >= 400;
|
||||
final wide = _overviewUsesWideLayout(
|
||||
context,
|
||||
constraints,
|
||||
minWidth: 400,
|
||||
);
|
||||
final valueStyle = GoogleFonts.poppins(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
@ -519,7 +532,7 @@ class ClaimsPremiumMembershipBody extends StatelessWidget {
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth < 520) {
|
||||
if (!_overviewUsesWideLayout(context, constraints)) {
|
||||
return Column(
|
||||
children: [
|
||||
premiumTile,
|
||||
|
||||
@ -69,7 +69,7 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
late ApiService apiService;
|
||||
|
||||
int _currentPage = 1;
|
||||
int _rowsPerPage = 5;
|
||||
int _rowsPerPage = 6;
|
||||
|
||||
List<List<Map<String, dynamic>>> get _paginatedExcelData {
|
||||
final start = (_currentPage - 1) * _rowsPerPage;
|
||||
@ -104,47 +104,45 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
final response =
|
||||
await apiService.getExcelFileErrorsApi(widget.id, widget.TokenType);
|
||||
|
||||
// 🔴 CASE 1: Empty data → popup + back
|
||||
if (response['data'] is List && response['data'].isEmpty) {
|
||||
if (!hasExcelErrorTableData(response)) {
|
||||
if (!mounted) return;
|
||||
setState(() => isLoading = false);
|
||||
_showEmptyDataDialog(response['message']);
|
||||
_showEmptyDataDialog(
|
||||
response['message']?.toString() ?? 'No error data available',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🟢 CASE 2: Success with data
|
||||
if (response['status'] == true) {
|
||||
if (response['message'] == "Error data feteched successfully") {
|
||||
// This runs if the message matches EXACTLY (including the typo 'feteched')
|
||||
if (response['message'] == 'Error data feteched successfully') {
|
||||
ToastHelper.showErrorToast(context, response['message']);
|
||||
} else {
|
||||
ToastHelper.showSuccessToast(context, response['message']);
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
isSuccess = false;
|
||||
excelValidationStaus = 1;
|
||||
|
||||
excelHeader = List<String>.from(response['data']['excel_header']);
|
||||
excelHeader =
|
||||
List<String>.from(response['data']['excel_header'] as List);
|
||||
|
||||
excelData = (response['data']['excel_data'] as List)
|
||||
.map<List<Map<String, dynamic>>>(
|
||||
(row) => row
|
||||
.map<Map<String, dynamic>>(
|
||||
(cell) => Map<String, dynamic>.from(cell))
|
||||
(cell) => Map<String, dynamic>.from(cell as Map),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
.toList();
|
||||
|
||||
filteredExcelData = List.from(excelData);
|
||||
_currentPage = 1;
|
||||
});
|
||||
}
|
||||
// 🟡 CASE 3: API failed with message
|
||||
else {
|
||||
setState(() => isLoading = false);
|
||||
_showEmptyDataDialog(response['message']);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => isLoading = false);
|
||||
logDebug('Exception occurred: $e');
|
||||
_showEmptyDataDialog('Something went wrong. Please try again.');
|
||||
@ -152,30 +150,10 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
}
|
||||
|
||||
void _showEmptyDataDialog(String message) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
title: const Text(
|
||||
'Message',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // close dialog
|
||||
Navigator.of(context).pop(); // go back page
|
||||
},
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
showExcelErrorMessageDialog(
|
||||
context,
|
||||
message,
|
||||
popRouteOnClose: true,
|
||||
);
|
||||
}
|
||||
|
||||
@ -295,18 +273,20 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return isLoading
|
||||
? Container(
|
||||
color: Color(0x98FFFCE5), // Semi-transparent background
|
||||
if (isLoading || excelHeader.isEmpty) {
|
||||
return Container(
|
||||
color: const Color(0x98FFFCE5),
|
||||
child: Center(
|
||||
child: // Your GIF loader widget
|
||||
Image.asset(
|
||||
child: Image.asset(
|
||||
height: 60,
|
||||
width: 60,
|
||||
'assets/nhance-loader.gif'), // Adjust path to your GIF loader
|
||||
'assets/nhance-loader.gif',
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
// padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50),
|
||||
child: Column(
|
||||
children: [
|
||||
@ -442,7 +422,11 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
}
|
||||
|
||||
Widget _buildCDDataTable(BuildContext context) {
|
||||
return ScrollConfiguration(
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
@ -451,19 +435,230 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
},
|
||||
),
|
||||
child: _buildScrollableTable(context),
|
||||
),
|
||||
),
|
||||
_buildPagination(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static const Map<String, double> _staticColumnWidthByHeader = {
|
||||
'sno': 56,
|
||||
'emp code': 128,
|
||||
'name': 140,
|
||||
'doj': 128,
|
||||
'gender': 72,
|
||||
'relation': 140,
|
||||
'relationship': 140,
|
||||
'dob': 152,
|
||||
'mail': 220,
|
||||
'mobile': 128,
|
||||
'si': 96,
|
||||
'grade': 96,
|
||||
'basic pay': 100,
|
||||
'unit': 80,
|
||||
'doc': 128,
|
||||
};
|
||||
|
||||
double _staticColumnWidth(String header) {
|
||||
return _staticColumnWidthByHeader[header.trim().toLowerCase()] ?? 120;
|
||||
}
|
||||
|
||||
Map<int, TableColumnWidth> get _tableColumnWidths {
|
||||
return {
|
||||
for (var i = 0; i < excelHeader.length; i++)
|
||||
i: FixedColumnWidth(_staticColumnWidth(excelHeader[i])),
|
||||
};
|
||||
}
|
||||
|
||||
double get _totalTableWidth {
|
||||
return excelHeader.fold<double>(
|
||||
0,
|
||||
(sum, header) => sum + _staticColumnWidth(header),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _ellipsizedText(
|
||||
String value, {
|
||||
TextStyle? style,
|
||||
}) {
|
||||
final text = Text(
|
||||
value,
|
||||
style: style,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
|
||||
if (value.isEmpty || value == '-') {
|
||||
return text;
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: value,
|
||||
waitDuration: const Duration(milliseconds: 300),
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tableHeaderCell(String header) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: _ellipsizedText(
|
||||
header,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tableDataCell(Map<String, dynamic> cell) {
|
||||
final value = cell['value']?.toString() ?? '-';
|
||||
final textStyle = GoogleFonts.poppins(fontSize: 12);
|
||||
final hasError = cell.containsKey('error');
|
||||
|
||||
if (!hasError) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: _ellipsizedText(value, style: textStyle),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _ellipsizedText(value, style: textStyle),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
|
||||
icon: const Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () => _showCellErrorDialog(cell['error'] as List),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCellErrorDialog(List errors) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
width: 420,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Error!',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFE0002A),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'!',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
errors.isNotEmpty
|
||||
? errors.first.toString()
|
||||
: 'Validation Error',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...errors.skip(1).map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
e.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
color: const Color(0xFFE09B2D),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 30,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE0002A),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScrollableTable(BuildContext context) {
|
||||
const double columnWidth = 160;
|
||||
final double tableWidth = excelHeader.length * columnWidth;
|
||||
final tableWidth = _totalTableWidth;
|
||||
|
||||
return Scrollbar(
|
||||
thumbVisibility: true,
|
||||
controller: _verticalController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _verticalController,
|
||||
physics: const ClampingScrollPhysics(), // 👈 mouse wheel
|
||||
physics: const ClampingScrollPhysics(),
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Scrollbar(
|
||||
thumbVisibility: true,
|
||||
@ -473,291 +668,150 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
controller: _horizontalController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: tableWidth,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: tableWidth),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD7E9EB),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: excelHeader.map((header) {
|
||||
return SizedBox(
|
||||
width: columnWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
header,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
|
||||
/// ROWS
|
||||
..._paginatedExcelData.map((row) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Table(
|
||||
columnWidths: _tableColumnWidths,
|
||||
defaultVerticalAlignment:
|
||||
TableCellVerticalAlignment.middle,
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Color(0xFFD7E9EB),
|
||||
),
|
||||
children: excelHeader
|
||||
.map(_tableHeaderCell)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Table(
|
||||
columnWidths: _tableColumnWidths,
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
border: const TableBorder(
|
||||
horizontalInside: BorderSide(
|
||||
color: Color(0xFFA9D9DE),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
children: _paginatedExcelData.map((row) {
|
||||
return TableRow(
|
||||
children: List.generate(excelHeader.length, (index) {
|
||||
final cell = index < row.length
|
||||
? row[index]
|
||||
: <String, dynamic>{'value': '-'};
|
||||
return _tableDataCell(cell);
|
||||
}),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
child: Row(
|
||||
children: row.map((cell) {
|
||||
final bool hasError = cell.containsKey('error');
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: columnWidth,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: hasError
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
cell['value']?.toString() ?? '-',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
IconButton(
|
||||
Widget _buildPageButton(int page) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _currentPage == page
|
||||
? const Color(0xFF00A6A6)
|
||||
: Colors.grey[300],
|
||||
foregroundColor: _currentPage == page ? Colors.white : Colors.black,
|
||||
minimumSize: const Size(36, 36),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
icon: const Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (_) {
|
||||
final List errors =
|
||||
cell['error'] as List;
|
||||
|
||||
return Dialog(
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
child: Container(
|
||||
width: 420,
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 28),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
/// ERROR TITLE
|
||||
Text(
|
||||
'Error!',
|
||||
style: GoogleFonts
|
||||
.poppins(
|
||||
fontSize: 32,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 16),
|
||||
|
||||
/// RED ICON
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration:
|
||||
const BoxDecoration(
|
||||
color: Color(
|
||||
0xFFE0002A),
|
||||
shape:
|
||||
BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'!',
|
||||
style: TextStyle(
|
||||
color: Colors
|
||||
.white,
|
||||
fontSize: 36,
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 20),
|
||||
|
||||
/// ERROR HEADING (optional – first error)
|
||||
Text(
|
||||
errors.isNotEmpty
|
||||
? errors.first
|
||||
.toString()
|
||||
: 'Validation Error',
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: GoogleFonts
|
||||
.poppins(
|
||||
fontSize: 18,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 12),
|
||||
|
||||
/// ERROR DETAILS
|
||||
...errors.skip(1).map(
|
||||
(e) => Padding(
|
||||
padding:
|
||||
const EdgeInsets
|
||||
.only(
|
||||
top: 6),
|
||||
child: Text(
|
||||
e.toString(),
|
||||
textAlign:
|
||||
TextAlign
|
||||
.center,
|
||||
style: GoogleFonts
|
||||
.poppins(
|
||||
fontSize:
|
||||
14,
|
||||
color: const Color(
|
||||
0xFFE09B2D), // orange text
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 20),
|
||||
|
||||
/// OK BUTTON
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 30,
|
||||
child: ElevatedButton(
|
||||
onPressed: () =>
|
||||
Navigator.pop(
|
||||
context),
|
||||
style:
|
||||
ElevatedButton
|
||||
.styleFrom(
|
||||
backgroundColor:
|
||||
const Color(
|
||||
0xFFE0002A),
|
||||
shape:
|
||||
RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(
|
||||
20),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: GoogleFonts
|
||||
.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.w600,
|
||||
color: Colors
|
||||
.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_currentPage = page;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
cell['value']?.toString() ?? '-',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
/// PAGINATION
|
||||
SizedBox(
|
||||
width: tableWidth,
|
||||
child: _buildPagination(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(page.toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPagination(BuildContext context) {
|
||||
final totalPages = (filteredExcelData.length / _rowsPerPage).ceil();
|
||||
final totalItems = filteredExcelData.length;
|
||||
final int startEntry =
|
||||
totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1;
|
||||
int endEntry = _currentPage * _rowsPerPage;
|
||||
if (endEntry > totalItems) endEntry = totalItems;
|
||||
|
||||
if (totalPages <= 1) {
|
||||
return const SizedBox.shrink(); // 👈 hide if only one page
|
||||
final totalPages =
|
||||
totalItems == 0 ? 1 : (filteredExcelData.length / _rowsPerPage).ceil();
|
||||
const visiblePageCount = 6;
|
||||
|
||||
List<int> getVisiblePages() {
|
||||
if (totalPages <= visiblePageCount) {
|
||||
return List.generate(totalPages, (i) => i + 1);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end, // 👈 right aligned
|
||||
if (_currentPage <= 3) {
|
||||
return [1, 2, 3, 4, 5];
|
||||
}
|
||||
if (_currentPage >= totalPages - 2) {
|
||||
return [
|
||||
totalPages - 4,
|
||||
totalPages - 3,
|
||||
totalPages - 2,
|
||||
totalPages - 1,
|
||||
totalPages,
|
||||
];
|
||||
}
|
||||
return [
|
||||
_currentPage - 2,
|
||||
_currentPage - 1,
|
||||
_currentPage,
|
||||
_currentPage + 1,
|
||||
_currentPage + 2,
|
||||
];
|
||||
}
|
||||
|
||||
final visiblePages = getVisiblePages();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Showing $startEntry to $endEntry of $totalItems entries',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
color: const Color(0xFF585757),
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
DropdownButton<int>(
|
||||
value: _rowsPerPage,
|
||||
items: [5, 10, 15, 20, 50].map((int value) {
|
||||
items: [6, 10, 15, 20, 50].map((int value) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: value,
|
||||
child: Text(
|
||||
' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 14),
|
||||
style: GoogleFonts.poppins(fontSize: 15),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
onChanged: totalItems == 0
|
||||
? null
|
||||
: (newValue) {
|
||||
setState(() {
|
||||
_rowsPerPage = newValue!;
|
||||
_currentPage = 1;
|
||||
@ -765,38 +819,154 @@ class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _currentPage > 1
|
||||
? () => setState(() => _currentPage--)
|
||||
: null,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed:
|
||||
_currentPage > 1 ? () => setState(() => _currentPage--) : null,
|
||||
),
|
||||
for (int i = 1; i <= totalPages; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _currentPage == i
|
||||
? const Color(0xFF00A6A6)
|
||||
: Colors.grey[300],
|
||||
foregroundColor:
|
||||
_currentPage == i ? Colors.white : Colors.black,
|
||||
minimumSize: const Size(36, 36),
|
||||
padding: EdgeInsets.zero,
|
||||
if (totalPages > 0 && !visiblePages.contains(1))
|
||||
Row(
|
||||
children: [
|
||||
_buildPageButton(1),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text('...'),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_currentPage = i;
|
||||
});
|
||||
},
|
||||
child: Text(i.toString()),
|
||||
],
|
||||
),
|
||||
for (final page in visiblePages) _buildPageButton(page),
|
||||
if (totalPages > 0 && !visiblePages.contains(totalPages))
|
||||
Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text('...'),
|
||||
),
|
||||
_buildPageButton(totalPages),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: _currentPage < totalPages
|
||||
? () => setState(() => _currentPage++)
|
||||
: null,
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasExcelErrorTableData(Map<String, dynamic> response) {
|
||||
if (response['status'] != true) return false;
|
||||
|
||||
final data = response['data'];
|
||||
if (data is! Map) return false;
|
||||
|
||||
final header = data['excel_header'];
|
||||
return header is List && header.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> showExcelErrorMessageDialog(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
bool popRouteOnClose = false,
|
||||
}) {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
title: const Text(
|
||||
'Message',
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop();
|
||||
if (popRouteOnClose && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openExcelErrorScreenIfAvailable({
|
||||
required BuildContext context,
|
||||
required ApiService apiService,
|
||||
required String fileId,
|
||||
required String tokenType,
|
||||
required String clientId,
|
||||
required String policyNo,
|
||||
required String action,
|
||||
required String createdAt,
|
||||
required String clientBranchId,
|
||||
required String token,
|
||||
}) async {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const PopScope(
|
||||
canPop: false,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await apiService.getExcelFileErrorsApi(fileId, tokenType);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
}
|
||||
|
||||
if (!hasExcelErrorTableData(response)) {
|
||||
if (context.mounted) {
|
||||
await showExcelErrorMessageDialog(
|
||||
context,
|
||||
response['message']?.toString() ?? 'No error data available',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
await Navigator.push<void>(
|
||||
context,
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => excelErrorScreen(
|
||||
ClientId: clientId,
|
||||
policy_no: policyNo,
|
||||
action: action,
|
||||
created_at: createdAt,
|
||||
clientBranchId: clientBranchId,
|
||||
Token: token,
|
||||
TokenType: tokenType,
|
||||
id: fileId,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
final navigator = Navigator.of(context, rootNavigator: true);
|
||||
if (navigator.canPop()) {
|
||||
navigator.pop();
|
||||
}
|
||||
await showExcelErrorMessageDialog(
|
||||
context,
|
||||
'Something went wrong. Please try again.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,7 +566,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
case 'emp_count':
|
||||
return relationship == 'self';
|
||||
case 'enrolled':
|
||||
return _isEnrolledStatus(status);
|
||||
return _isSelfRow(row) && _isEnrolledStatus(status);
|
||||
case 'not_enrolled':
|
||||
return !_isEnrolledStatus(status);
|
||||
case 'logged_in':
|
||||
@ -574,7 +574,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
case 'not_logged_in':
|
||||
return _isSelfRow(row) && !_isLoggedInRow(row);
|
||||
case 'draft':
|
||||
return status == 'draft';
|
||||
return _isSelfRow(row) && status == 'draft';
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@ -1934,9 +1934,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
item['relationship']?.toString().toLowerCase().trim() ?? '';
|
||||
|
||||
if (relationship == 'self') empCount++;
|
||||
if (_isEnrolledStatus(status)) {
|
||||
if (_isSelfRow(item) && _isEnrolledStatus(status)) {
|
||||
enrolled++;
|
||||
} else {
|
||||
} else if (!_isEnrolledStatus(status)) {
|
||||
notEnrolled++;
|
||||
}
|
||||
if (_isSelfRow(item)) {
|
||||
@ -1946,7 +1946,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
notLoggedIn++;
|
||||
}
|
||||
}
|
||||
if (status == 'draft') draft++;
|
||||
if (status == 'draft' && _isSelfRow(item)) draft++;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -900,38 +900,29 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
/// Select File Action
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: buildStyledDropdown(
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final narrow = constraints.maxWidth < 640;
|
||||
final fileActionDropdown = buildStyledDropdown(
|
||||
label: 'Select File Action',
|
||||
value: selectedKey,
|
||||
items: getFileUploadMasterList,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
selectedKey = val;
|
||||
final selectedItem = getFileUploadMasterList
|
||||
.firstWhere((e) => e['key'] == val);
|
||||
final selectedItem =
|
||||
getFileUploadMasterList.firstWhere(
|
||||
(e) => e['key'] == val,
|
||||
);
|
||||
selectedValue = selectedItem['value'];
|
||||
currentApiValue = selectedItem['key'];
|
||||
showSampleButton = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 16),
|
||||
|
||||
/// Upload Box
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
);
|
||||
final uploadField = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
/// ✅ LABEL
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: 'Upload File',
|
||||
@ -952,10 +943,7 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
|
||||
/// ✅ DOTTED UPLOAD BOX
|
||||
DragTarget<html.File>(
|
||||
onAccept: (html.File droppedFile) {
|
||||
setState(() {
|
||||
@ -979,11 +967,11 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12),
|
||||
horizontal: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius:
|
||||
BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF00A6A6),
|
||||
width: 1,
|
||||
@ -995,8 +983,7 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
child: Text(
|
||||
fileName ??
|
||||
'Upload Your Documents',
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
color: fileName == null
|
||||
@ -1012,13 +999,39 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
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),
|
||||
Column(
|
||||
@ -1146,15 +1159,21 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
);
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
final crossAxisCount = width < 640 ? 1 : 2;
|
||||
const mainAxisExtent = 88.0;
|
||||
|
||||
return GridView.builder(
|
||||
shrinkWrap: true, // ✅ IMPORTANT
|
||||
physics: const NeverScrollableScrollPhysics(), // ✅ Disable inner scroll
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 10,
|
||||
mainAxisExtent: mainAxisExtent,
|
||||
),
|
||||
itemCount: _paginatedData.length,
|
||||
itemBuilder: (context, index) {
|
||||
@ -1162,11 +1181,13 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
return _buildFileCard(item);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFileCard(Map<String, dynamic> item) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF9FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@ -1175,10 +1196,9 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
/// 📄 File Icon
|
||||
Container(
|
||||
height: 44,
|
||||
width: 44,
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
@ -1187,18 +1207,16 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
child: const Icon(
|
||||
Icons.description_outlined,
|
||||
color: Color(0xFF00A6A6),
|
||||
size: 22,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
/// 📑 LEFT CONTENT
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
/// Row 1 → File name
|
||||
Text(
|
||||
item['file_name'] ?? '-',
|
||||
maxLines: 1,
|
||||
@ -1209,12 +1227,9 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
color: const Color(0xFF101010),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
/// Row 2 → Action - Date
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: GoogleFonts.poppins(fontSize: 11),
|
||||
children: [
|
||||
TextSpan(
|
||||
@ -1234,30 +1249,21 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD)
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
/// 🔴 Error + Status
|
||||
Row(
|
||||
children: [],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item['file_error_status'] == '1')
|
||||
InkWell(
|
||||
Tooltip(
|
||||
message: 'Info',
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
logDebug(item);
|
||||
// return;
|
||||
final String? token =
|
||||
await tokenService.getCurrentToken();
|
||||
final String? empClientId =
|
||||
@ -1265,75 +1271,60 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
final String? empBranchId =
|
||||
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}');
|
||||
'❌ 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'],
|
||||
if (!context.mounted) return;
|
||||
await openExcelErrorScreenIfAvailable(
|
||||
context: context,
|
||||
apiService: apiService,
|
||||
fileId: item['id'].toString(),
|
||||
tokenType: 'post',
|
||||
clientId: empClientId,
|
||||
policyNo: item['policy_no']?.toString() ?? '',
|
||||
action: item['file_action']?.toString() ?? '',
|
||||
createdAt: item['created_at']?.toString() ?? '',
|
||||
clientBranchId: empBranchId,
|
||||
Token: token,
|
||||
TokenType: 'post',
|
||||
id: item['id']),
|
||||
),
|
||||
token: token,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
child: const Icon(
|
||||
Icons.error,
|
||||
size: 16,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatusChip(item['status']),
|
||||
SizedBox(width: 10),
|
||||
InkWell(
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: 'Download',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
getHrFileDownload(item['id'], item['file_name']);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
height: 28,
|
||||
width: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFC5F2F4),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFF76CED2)),
|
||||
),
|
||||
child: Icon(
|
||||
child: const Icon(
|
||||
Icons.file_download_outlined,
|
||||
color: Color(0xFF1D1B20),
|
||||
size: 22,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// ⬇ Download
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -1428,21 +1419,18 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
|
||||
List<int> visiblePages = getVisiblePages();
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
Widget paginationControls = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
// Dropdown for rows per page
|
||||
DropdownButton<int>(
|
||||
value: _rowsPerPage,
|
||||
items: [6, 10, 15, 20, 50].map((int value) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: value,
|
||||
child: Text(' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 15)),
|
||||
child: Text(
|
||||
' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 15),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
@ -1452,40 +1440,33 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
// Previous button
|
||||
IconButton(
|
||||
tooltip: 'Previous Page',
|
||||
onPressed: _currentPage > 1
|
||||
? () => setState(() => _currentPage--)
|
||||
: null,
|
||||
onPressed:
|
||||
_currentPage > 1 ? () => setState(() => _currentPage--) : null,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
),
|
||||
|
||||
// First page + left ellipsis
|
||||
if (!visiblePages.contains(1))
|
||||
Row(children: [
|
||||
Row(
|
||||
children: [
|
||||
_buildPageButton(1),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text("..."),
|
||||
child: Text('...'),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
|
||||
// Visible page buttons
|
||||
for (int page in visiblePages) _buildPageButton(page),
|
||||
|
||||
// Right ellipsis + last page
|
||||
if (!visiblePages.contains(totalPages))
|
||||
Row(children: [
|
||||
if (!visiblePages.contains(totalPages) && totalPages > 0)
|
||||
Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text("..."),
|
||||
child: Text('...'),
|
||||
),
|
||||
_buildPageButton(totalPages),
|
||||
]),
|
||||
|
||||
// Next button
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _currentPage < totalPages
|
||||
? () => setState(() => _currentPage++)
|
||||
@ -1493,12 +1474,56 @@ class _postFileUploadState extends State<postFileUpload> {
|
||||
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) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
|
||||
@ -930,10 +930,77 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final narrow = constraints.maxWidth < 580;
|
||||
if (narrow) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_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(height: 16),
|
||||
_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);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 260, // 👈 set your required width
|
||||
width: 260,
|
||||
child: _dateField(
|
||||
label: 'Enrolment Open Date',
|
||||
controller: openDateController,
|
||||
@ -946,10 +1013,11 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
);
|
||||
if (picked != null) {
|
||||
final formatted =
|
||||
DateFormat('dd-MM-yyyy').format(picked);
|
||||
DateFormat('dd-MM-yyyy')
|
||||
.format(picked);
|
||||
|
||||
// ✅ If open date changed, clear close date
|
||||
if (openDateController.text != formatted) {
|
||||
if (openDateController.text !=
|
||||
formatted) {
|
||||
closeDateController.clear();
|
||||
}
|
||||
|
||||
@ -960,36 +1028,42 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 260, // 👈 same width
|
||||
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');
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
'Please select Enrolment Open Date first',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final openDate = DateFormat('dd-MM-yyyy')
|
||||
.parse(openDateController.text);
|
||||
final openDate =
|
||||
DateFormat('dd-MM-yyyy').parse(
|
||||
openDateController.text,
|
||||
);
|
||||
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
firstDate:
|
||||
openDate, // ✅ Cannot select before open date
|
||||
firstDate: openDate,
|
||||
lastDate: DateTime(2100),
|
||||
initialDate: openDate,
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
closeDateController.text =
|
||||
DateFormat('dd-MM-yyyy').format(picked);
|
||||
DateFormat('dd-MM-yyyy')
|
||||
.format(picked);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Row(
|
||||
@ -1288,15 +1362,21 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
return const Center(child: Text('No uploaded files'));
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
final crossAxisCount = width < 640 ? 1 : 2;
|
||||
const mainAxisExtent = 88.0;
|
||||
|
||||
return GridView.builder(
|
||||
shrinkWrap: true, // ✅ IMPORTANT
|
||||
physics: const NeverScrollableScrollPhysics(), // ✅ Disable inner scroll
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 10,
|
||||
mainAxisExtent: mainAxisExtent,
|
||||
),
|
||||
itemCount: _paginatedData.length,
|
||||
itemBuilder: (context, index) {
|
||||
@ -1304,6 +1384,8 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
return _buildFileCard(item);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Widget _buildFileUploadedGrid() {
|
||||
@ -1333,7 +1415,7 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
|
||||
Widget _buildFileCard(Map<String, dynamic> item) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF9FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@ -1342,10 +1424,9 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
/// 📄 File Icon
|
||||
Container(
|
||||
height: 44,
|
||||
width: 44,
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
@ -1354,18 +1435,16 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
child: const Icon(
|
||||
Icons.description_outlined,
|
||||
color: Color(0xFF00A6A6),
|
||||
size: 22,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
/// 📑 LEFT CONTENT
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
/// Row 1 → File name
|
||||
Text(
|
||||
item['file_name'] ?? '-',
|
||||
maxLines: 1,
|
||||
@ -1376,12 +1455,9 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
color: const Color(0xFF101010),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
/// Row 2 → Action - Date
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: GoogleFonts.poppins(fontSize: 11),
|
||||
children: [
|
||||
TextSpan(
|
||||
@ -1401,32 +1477,21 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD)
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
/// 🔴 Error + Status
|
||||
Row(
|
||||
children: [],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item['file_error_status'] == '1')
|
||||
Tooltip(
|
||||
message: 'Info', // Added tooltip name
|
||||
message: 'Info',
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
logDebug(item);
|
||||
// return;
|
||||
final String? token =
|
||||
await tokenService.getCurrentToken();
|
||||
final String? enrollmentClient_id = await tokenService
|
||||
@ -1435,81 +1500,62 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
await tokenService
|
||||
.readValue('enrollmentEmpClientBranchId');
|
||||
|
||||
logDebug(item);
|
||||
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}');
|
||||
'❌ 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'],
|
||||
if (!context.mounted) return;
|
||||
await openExcelErrorScreenIfAvailable(
|
||||
context: context,
|
||||
apiService: apiService,
|
||||
fileId: item['id'].toString(),
|
||||
tokenType: 'pre',
|
||||
clientId: enrollmentClient_id,
|
||||
policyNo: item['policy_no']?.toString() ?? '',
|
||||
action: item['file_action']?.toString() ?? '',
|
||||
createdAt: item['created_at']?.toString() ?? '',
|
||||
clientBranchId: enrollmentEmpClientBranchId,
|
||||
Token: token,
|
||||
TokenType: 'pre',
|
||||
id: item['id']),
|
||||
),
|
||||
token: token,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
child: const Icon(
|
||||
Icons.error,
|
||||
size: 16,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatusChip(item['status']),
|
||||
SizedBox(width: 10),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: 'Download', // Added tooltip name
|
||||
message: 'Download',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
getHrFileDownload(item['id'], item['file_name']);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
height: 28,
|
||||
width: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFC5F2F4),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFF76CED2)),
|
||||
),
|
||||
child: Icon(
|
||||
child: const Icon(
|
||||
Icons.file_download_outlined,
|
||||
color: Color(0xFF1D1B20),
|
||||
size: 22,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// ⬇ Download
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -1586,35 +1632,18 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
|
||||
List<int> visiblePages = getVisiblePages();
|
||||
|
||||
return Padding(
|
||||
// Match this horizontal padding (16) to your Table Header padding for perfect alignment
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment
|
||||
.spaceBetween, // Pushes text to left, buttons to right
|
||||
Widget paginationControls = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// --- LEFT SIDE: Showing Text ---
|
||||
Text(
|
||||
"Showing $startEntry to $endEntry of $totalItems entries",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
color: const Color(0xFF585757),
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
|
||||
// --- RIGHT SIDE: Controls ---
|
||||
Row(
|
||||
children: [
|
||||
// Dropdown for rows per page
|
||||
DropdownButton<int>(
|
||||
value: _rowsPerPage,
|
||||
// focusColor: Colors.transparent, // Fix: Removes the grey/blue highlight on change
|
||||
items: [6, 10, 15, 20, 50].map((int value) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: value,
|
||||
child: Text(' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 15)),
|
||||
child: Text(
|
||||
' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 15),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
@ -1624,39 +1653,32 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
// Previous button
|
||||
IconButton(
|
||||
onPressed: _currentPage > 1
|
||||
? () => setState(() => _currentPage--)
|
||||
: null,
|
||||
onPressed:
|
||||
_currentPage > 1 ? () => setState(() => _currentPage--) : null,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
),
|
||||
|
||||
// First page + left ellipsis
|
||||
if (!visiblePages.contains(1))
|
||||
Row(children: [
|
||||
Row(
|
||||
children: [
|
||||
_buildPageButton(1),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text("..."),
|
||||
child: Text('...'),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
|
||||
// Visible page buttons
|
||||
for (int page in visiblePages) _buildPageButton(page),
|
||||
|
||||
// Right ellipsis + last page
|
||||
if (!visiblePages.contains(totalPages) && totalPages > 0)
|
||||
Row(children: [
|
||||
Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text("..."),
|
||||
child: Text('...'),
|
||||
),
|
||||
_buildPageButton(totalPages),
|
||||
]),
|
||||
|
||||
// Next button
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _currentPage < totalPages
|
||||
? () => setState(() => _currentPage++)
|
||||
@ -1664,8 +1686,52 @@ class _excelVerifyState extends State<preFileUpload> {
|
||||
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 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../presentation/claims_overview/claims_overview_cache.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class TokenStorageService {
|
||||
@ -268,7 +270,10 @@ class TokenStorageService {
|
||||
// 1️⃣ Clear ONLY branch/session related keys
|
||||
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);
|
||||
|
||||
// 3️⃣ Rebuild decoded session data from token
|
||||
|
||||
Loading…
Reference in New Issue
Block a user