enrollment-app/lib/presentation/excelVerification.dart
2026-02-05 19:15:46 +05:30

768 lines
28 KiB
Dart
Raw 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.

This file contains Unicode characters that might be confused with other characters. 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';
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 = 5;
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);
// 🔴 CASE 1: Empty data → popup + back
if (response['data'] is List && response['data'].isEmpty) {
setState(() => isLoading = false);
_showEmptyDataDialog(response['message']);
return;
}
// 🟢 CASE 2: Success with data
if (response['status'] == true) {
ToastHelper.showSuccessToast(context, response['message']);
setState(() {
isLoading = false;
isSuccess = false;
excelValidationStaus = 1;
excelHeader =
List<String>.from(response['data']['excel_header']);
excelData = (response['data']['excel_data'] as List)
.map<List<Map<String, dynamic>>>(
(row) => row
.map<Map<String, dynamic>>(
(cell) => Map<String, dynamic>.from(cell))
.toList(),
)
.toList();
filteredExcelData = List.from(excelData);
});
}
// 🟡 CASE 3: API failed with message
else {
setState(() => isLoading = false);
_showEmptyDataDialog(response['message']);
}
} catch (e) {
setState(() => isLoading = false);
print('Exception occurred: $e');
_showEmptyDataDialog('Something went wrong. Please try again.');
}
}
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'),
),
],
);
},
);
}
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 {
// print('handleExportAction');
//
// final postId = await tokenService.readValue('empHrId');
// final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
//
// var activity = "export_empdata";
//
// var activityPre = "export_preempdata";
// dynamic response;
//
// print('postId - $postId');
// print('preId - $preId');
// print('activity - $activity');
//
// try {
// print('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') {
// print('Request success');
// } else {
// // ToastHelper.showWarningToast(
// // context, 'Request failed with status: ${response.statusCode}');
// print('Request failed with status: ${response['code']}');
// }
// } catch (e) {
// print('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) {
return isLoading
? Container(
color: Color(0x98FFFCE5), // Semi-transparent background
child: Center(
child: // Your GIF loader widget
Image.asset(
height: 60,
width: 60,
'assets/nhance-loader.gif'), // Adjust path to your GIF loader
),
)
: 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 ScrollConfiguration(
behavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
},
),
child: _buildScrollableTable(context),
);
}
Widget _buildScrollableTable(BuildContext context) {
const double columnWidth = 160;
final double tableWidth = excelHeader.length * columnWidth;
return Scrollbar(
thumbVisibility: true,
controller: _verticalController,
child: SingleChildScrollView(
controller: _verticalController,
physics: const ClampingScrollPhysics(), // 👈 mouse wheel
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: SizedBox(
width: tableWidth,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: const Color(0xFFD7E9EB),
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),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color(0xFFA9D9DE),
width: 1,
),
),
),
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(
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,
),
),
),
),
],
),
),
);
},
);
},
),
],
)
: Text(
cell['value']?.toString() ?? '-',
style: GoogleFonts.poppins(
fontSize: 12,
),
),
),
);
}).toList(),
),
);
}).toList(),
/// PAGINATION
SizedBox(
width: tableWidth,
child: _buildPagination(context),
),
],
),
),
),
),
),
);
}
Widget _buildPagination(BuildContext context) {
final totalPages =
(filteredExcelData.length / _rowsPerPage).ceil();
if (totalPages <= 1) {
return const SizedBox.shrink(); // 👈 hide if only one page
}
return Row(
mainAxisAlignment: MainAxisAlignment.end, // 👈 right aligned
children: [
DropdownButton<int>(
value: _rowsPerPage,
items: [5, 10, 15, 20, 50].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(
' $value ',
style: GoogleFonts.poppins(fontSize: 14),
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_rowsPerPage = newValue!;
_currentPage = 1;
});
},
),
IconButton(
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,
),
onPressed: () {
setState(() {
_currentPage = i;
});
},
child: Text(i.toString()),
),
),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++)
: null,
),
],
);
}
}