enrollment-app/lib/presentation/excelVerification.dart

973 lines
30 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.',
);
}
}
}