973 lines
30 KiB
Dart
973 lines
30 KiB
Dart
import 'dart:ui';
|
||
|
||
import 'package:csv/csv.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:intl/intl.dart';
|
||
import 'package:nhancepolicy/presentation/preFileUpload.dart';
|
||
import 'package:nhancepolicy/presentation/postFileUpload.dart';
|
||
import 'package:nhancepolicy/service/api_service.dart';
|
||
import 'dart:convert';
|
||
import 'dart:async';
|
||
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
||
import 'package:nhancepolicy/service/token_storage_service.dart';
|
||
import 'package:universal_html/html.dart' as html;
|
||
import 'dart:typed_data';
|
||
import 'package:collection/collection.dart';
|
||
import 'package:url_launcher/url_launcher.dart';
|
||
import '../customAppBar/base_layout.dart';
|
||
import 'package:nhancepolicy/logger.dart';
|
||
|
||
class excelErrorScreen extends StatefulWidget {
|
||
final String ClientId;
|
||
final String policy_no;
|
||
final String action;
|
||
final String created_at;
|
||
final String clientBranchId;
|
||
final String Token;
|
||
final String TokenType;
|
||
final String id;
|
||
const excelErrorScreen({
|
||
Key? key,
|
||
required this.ClientId,
|
||
required this.policy_no,
|
||
required this.action,
|
||
required this.created_at,
|
||
required this.clientBranchId,
|
||
required this.Token,
|
||
required this.TokenType,
|
||
required this.id,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
State<excelErrorScreen> createState() => _activePolicyExcelErrorState();
|
||
}
|
||
|
||
class _activePolicyExcelErrorState extends State<excelErrorScreen>
|
||
with TickerProviderStateMixin {
|
||
final tokenService = TokenStorageService();
|
||
bool isLoading = false;
|
||
dynamic empPrimaryId;
|
||
dynamic empClientId;
|
||
dynamic empClientBranchId;
|
||
dynamic empHrId;
|
||
dynamic enrollmentClient_id;
|
||
dynamic enrollmentEmpClientBranchId;
|
||
dynamic enrollmentHrId;
|
||
TextEditingController searchController = TextEditingController();
|
||
List<String> excelHeader = [];
|
||
List<List<Map<String, dynamic>>> excelData = [];
|
||
|
||
late int excelValidationStaus = 1;
|
||
bool isSuccess = false;
|
||
String successContent = '';
|
||
|
||
List<List<Map<String, dynamic>>> filteredExcelData = [];
|
||
|
||
String? _postPreToken = '';
|
||
|
||
late ApiService apiService;
|
||
|
||
int _currentPage = 1;
|
||
int _rowsPerPage = 6;
|
||
|
||
List<List<Map<String, dynamic>>> get _paginatedExcelData {
|
||
final start = (_currentPage - 1) * _rowsPerPage;
|
||
final end =
|
||
(_currentPage * _rowsPerPage).clamp(0, filteredExcelData.length);
|
||
return filteredExcelData.sublist(start, end);
|
||
}
|
||
|
||
final ScrollController _verticalController = ScrollController();
|
||
final ScrollController _horizontalController = ScrollController();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
apiService = ApiService(context);
|
||
getCDPoliciesDetails();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_verticalController.dispose();
|
||
_horizontalController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> getCDPoliciesDetails() async {
|
||
setState(() {
|
||
isLoading = true;
|
||
});
|
||
|
||
try {
|
||
final response =
|
||
await apiService.getExcelFileErrorsApi(widget.id, widget.TokenType);
|
||
|
||
if (!hasExcelErrorTableData(response)) {
|
||
if (!mounted) return;
|
||
setState(() => isLoading = false);
|
||
_showEmptyDataDialog(
|
||
response['message']?.toString() ?? 'No error data available',
|
||
);
|
||
return;
|
||
}
|
||
|
||
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'] 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 as Map),
|
||
)
|
||
.toList(),
|
||
)
|
||
.toList();
|
||
|
||
filteredExcelData = List.from(excelData);
|
||
_currentPage = 1;
|
||
});
|
||
} catch (e) {
|
||
if (!mounted) return;
|
||
setState(() => isLoading = false);
|
||
logDebug('Exception occurred: $e');
|
||
_showEmptyDataDialog('Something went wrong. Please try again.');
|
||
}
|
||
}
|
||
|
||
void _showEmptyDataDialog(String message) {
|
||
showExcelErrorMessageDialog(
|
||
context,
|
||
message,
|
||
popRouteOnClose: true,
|
||
);
|
||
}
|
||
|
||
void search(String query) {
|
||
if (query.isEmpty) {
|
||
setState(() {
|
||
filteredExcelData = List.from(excelData);
|
||
_currentPage = 1;
|
||
});
|
||
return;
|
||
}
|
||
|
||
final lowerQuery = query.toLowerCase();
|
||
|
||
setState(() {
|
||
filteredExcelData = excelData.where((row) {
|
||
return row.any((cell) {
|
||
final value = cell['value'];
|
||
return value != null &&
|
||
value.toString().toLowerCase().contains(lowerQuery);
|
||
});
|
||
}).toList();
|
||
|
||
_currentPage = 1;
|
||
});
|
||
}
|
||
|
||
// emp_is_active
|
||
|
||
void exportToCsv({
|
||
required List<String> excelHeader,
|
||
required List<List<Map<String, dynamic>>> excelData,
|
||
}) {
|
||
List<List<String>> rows = [];
|
||
|
||
/// 1️⃣ Add headers
|
||
rows.add(excelHeader);
|
||
|
||
/// 2️⃣ Add rows
|
||
for (final row in excelData) {
|
||
rows.add(
|
||
row.map<String>((cell) {
|
||
final value = cell['value'];
|
||
return value == null ? '' : value.toString();
|
||
}).toList(),
|
||
);
|
||
}
|
||
|
||
/// 3️⃣ Convert to CSV
|
||
final csvData = const ListToCsvConverter().convert(rows);
|
||
|
||
/// 4️⃣ Download (Flutter Web)
|
||
final bytes = utf8.encode(csvData);
|
||
final blob = html.Blob([bytes], 'text/csv');
|
||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||
|
||
html.AnchorElement(href: url)
|
||
..setAttribute("download", "Excel_Error_File.csv")
|
||
..click();
|
||
|
||
html.Url.revokeObjectUrl(url);
|
||
}
|
||
|
||
// Future<void> handleExportAction() async {
|
||
// logDebug('handleExportAction');
|
||
//
|
||
// final postId = await tokenService.readValue('empHrId');
|
||
// final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
|
||
//
|
||
// var activity = "export_empdata";
|
||
//
|
||
// var activityPre = "export_preempdata";
|
||
// dynamic response;
|
||
//
|
||
// logDebug('postId - $postId');
|
||
// logDebug('preId - $preId');
|
||
// logDebug('activity - $activity');
|
||
//
|
||
// try {
|
||
// logDebug('10');
|
||
//
|
||
// if (widget.TokenType == 'pre') {
|
||
// response = await apiService.getPreLogHrActivity(
|
||
// postId!, preId!, widget.Token, activityPre);
|
||
// } else if (widget.TokenType == 'post') {
|
||
// response = await apiService.getPostLogHrActivity(
|
||
// postId!, preId!, widget.Token, activity);
|
||
// }
|
||
//
|
||
// if (response['status'] == 'success') {
|
||
// logDebug('Request success');
|
||
// } else {
|
||
// // ToastHelper.showWarningToast(
|
||
// // context, 'Request failed with status: ${response.statusCode}');
|
||
// logDebug('Request failed with status: ${response['code']}');
|
||
// }
|
||
// } catch (e) {
|
||
// logDebug('Exception occurred: $e');
|
||
// }
|
||
// }
|
||
|
||
String _capitalize(String? value) {
|
||
if (value == null || value.isEmpty) return '';
|
||
return value[0].toUpperCase() + value.substring(1).toLowerCase();
|
||
}
|
||
|
||
String formatDateTime(String dateTime) {
|
||
final parsedDate = DateTime.parse(dateTime);
|
||
return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return BaseLayout(
|
||
child: _buildContent(context),
|
||
);
|
||
}
|
||
|
||
Widget _buildContent(BuildContext context) {
|
||
if (isLoading || excelHeader.isEmpty) {
|
||
return Container(
|
||
color: const Color(0x98FFFCE5),
|
||
child: Center(
|
||
child: Image.asset(
|
||
height: 60,
|
||
width: 60,
|
||
'assets/nhance-loader.gif',
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
return Container(
|
||
// padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
/// 🔙 Back + Title (LEFT)
|
||
Row(
|
||
children: [
|
||
IconButton(
|
||
onPressed: () => {Navigator.pop(context)},
|
||
icon: const Icon(
|
||
Icons.arrow_back_ios,
|
||
size: 18,
|
||
color: Colors.black,
|
||
),
|
||
padding: EdgeInsets.zero,
|
||
constraints: const BoxConstraints(),
|
||
),
|
||
const SizedBox(width: 6),
|
||
Container(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
widget.policy_no ?? '',
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.black,
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
if (widget.TokenType != 'pre')
|
||
RichText(
|
||
text: TextSpan(
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w400,
|
||
),
|
||
children: [
|
||
TextSpan(
|
||
text: widget.action,
|
||
style: const TextStyle(
|
||
color: Color(0xFF00999E),
|
||
),
|
||
),
|
||
const TextSpan(
|
||
text: ' - ',
|
||
style: TextStyle(
|
||
color: Color(0xFF585858),
|
||
),
|
||
),
|
||
TextSpan(
|
||
text: formatDateTime(widget.created_at),
|
||
style: const TextStyle(
|
||
color: Color(0xFF585858),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
/// Push right content to end
|
||
const Spacer(),
|
||
|
||
/// 🔍 Search Box
|
||
Container(
|
||
width: 380,
|
||
height: 37,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF0F0F0),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: TextField(
|
||
controller: searchController,
|
||
onChanged: search,
|
||
style: GoogleFonts.poppins(fontSize: 14),
|
||
decoration: const InputDecoration(
|
||
hintText: 'Search',
|
||
prefixIcon: Icon(Icons.search, size: 18),
|
||
border: InputBorder.none,
|
||
contentPadding:
|
||
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||
),
|
||
),
|
||
),
|
||
|
||
const SizedBox(width: 12),
|
||
|
||
/// ⬇️ Export Button
|
||
SizedBox(
|
||
width: 116,
|
||
height: 37,
|
||
child: ElevatedButton(
|
||
onPressed: () {
|
||
exportToCsv(
|
||
excelHeader: excelHeader,
|
||
excelData: filteredExcelData, // or excelData
|
||
);
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFFE26728),
|
||
elevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
),
|
||
child: Text(
|
||
'Export',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.w700,
|
||
color: Colors.white,
|
||
letterSpacing: 1,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 20),
|
||
Expanded(
|
||
child: _buildCDDataTable(context),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCDDataTable(BuildContext context) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Expanded(
|
||
child: ScrollConfiguration(
|
||
behavior: const MaterialScrollBehavior().copyWith(
|
||
dragDevices: {
|
||
PointerDeviceKind.mouse,
|
||
PointerDeviceKind.touch,
|
||
PointerDeviceKind.trackpad,
|
||
},
|
||
),
|
||
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) {
|
||
final tableWidth = _totalTableWidth;
|
||
|
||
return Scrollbar(
|
||
thumbVisibility: true,
|
||
controller: _verticalController,
|
||
child: SingleChildScrollView(
|
||
controller: _verticalController,
|
||
physics: const ClampingScrollPhysics(),
|
||
scrollDirection: Axis.vertical,
|
||
child: Scrollbar(
|
||
thumbVisibility: true,
|
||
controller: _horizontalController,
|
||
notificationPredicate: (n) => n.depth == 1,
|
||
child: SingleChildScrollView(
|
||
controller: _horizontalController,
|
||
physics: const ClampingScrollPhysics(),
|
||
scrollDirection: Axis.horizontal,
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints.tightFor(width: tableWidth),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
ClipRRect(
|
||
borderRadius: BorderRadius.circular(6),
|
||
child: Table(
|
||
columnWidths: _tableColumnWidths,
|
||
defaultVerticalAlignment:
|
||
TableCellVerticalAlignment.middle,
|
||
children: [
|
||
TableRow(
|
||
decoration: const BoxDecoration(
|
||
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(),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPageButton(int page) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: _currentPage == page
|
||
? const Color(0xFF00A6A6)
|
||
: Colors.grey[300],
|
||
foregroundColor: _currentPage == page ? Colors.white : Colors.black,
|
||
minimumSize: const Size(36, 36),
|
||
padding: EdgeInsets.zero,
|
||
),
|
||
onPressed: () {
|
||
setState(() {
|
||
_currentPage = page;
|
||
});
|
||
},
|
||
child: Text(page.toString()),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPagination(BuildContext context) {
|
||
final totalItems = filteredExcelData.length;
|
||
final int startEntry =
|
||
totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1;
|
||
int endEntry = _currentPage * _rowsPerPage;
|
||
if (endEntry > totalItems) endEntry = totalItems;
|
||
|
||
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);
|
||
}
|
||
|
||
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: [6, 10, 15, 20, 50].map((int value) {
|
||
return DropdownMenuItem<int>(
|
||
value: value,
|
||
child: Text(
|
||
' $value ',
|
||
style: GoogleFonts.poppins(fontSize: 15),
|
||
),
|
||
);
|
||
}).toList(),
|
||
onChanged: totalItems == 0
|
||
? null
|
||
: (newValue) {
|
||
setState(() {
|
||
_rowsPerPage = newValue!;
|
||
_currentPage = 1;
|
||
});
|
||
},
|
||
),
|
||
IconButton(
|
||
onPressed: _currentPage > 1
|
||
? () => setState(() => _currentPage--)
|
||
: null,
|
||
icon: const Icon(Icons.chevron_left),
|
||
),
|
||
if (totalPages > 0 && !visiblePages.contains(1))
|
||
Row(
|
||
children: [
|
||
_buildPageButton(1),
|
||
const Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||
child: Text('...'),
|
||
),
|
||
],
|
||
),
|
||
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(
|
||
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.',
|
||
);
|
||
}
|
||
}
|
||
}
|