311 lines
10 KiB
Dart
311 lines
10 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:excel/excel.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
|
import 'package:open_file/open_file.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:universal_html/html.dart' as html;
|
|
|
|
import 'dart:typed_data';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
|
|
import 'dart:io' show File; // Only used in mobile/desktop
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:open_file/open_file.dart';
|
|
|
|
/// Optional per-field Excel cell formatter. Use when raw map values differ from
|
|
/// on-screen display (dates, status labels, etc.). Key must match [keys] entries.
|
|
typedef ExportValueFormatter = String Function(
|
|
dynamic value,
|
|
Map<String, dynamic> row,
|
|
);
|
|
|
|
class ExportBtn extends HookWidget {
|
|
final String sheetName;
|
|
final String fileName;
|
|
final List<Map<String, dynamic>> data;
|
|
final List<String>? displayHeaders; // dynamic headers
|
|
// final List<String> headers; // dynamic headers
|
|
final List<String>? keys; // dynamic headers
|
|
final bool? txt;
|
|
/// When set, these keys are exported as the returned string (not raw int/double).
|
|
final Map<String, ExportValueFormatter>? valueFormatters;
|
|
|
|
const ExportBtn({
|
|
super.key,
|
|
this.txt,
|
|
required this.sheetName,
|
|
required this.fileName,
|
|
required this.data,
|
|
|
|
// required this.headers,
|
|
required this.displayHeaders,
|
|
this.keys,
|
|
this.valueFormatters,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Tooltip(
|
|
message: 'Export File',
|
|
child: InkWell(
|
|
onTap: () async {
|
|
print('TAV ExcelExporter');
|
|
final scaffold = ScaffoldMessenger.of(context);
|
|
try {
|
|
await ExcelExporter.exportToExcel(
|
|
sheetName: sheetName,
|
|
data: data,
|
|
displayHeaders: displayHeaders!,
|
|
keys: keys!,
|
|
fileName: fileName,
|
|
valueFormatters: valueFormatters,
|
|
);
|
|
ToastHelper.showSuccessToast(context, 'Export finished');
|
|
} catch (e) {
|
|
ToastHelper.showErrorToast(context, 'Export failed: $e');
|
|
}
|
|
},
|
|
// onTap: () {
|
|
// print('TAV ExcelExporter');
|
|
//
|
|
// ExcelExporter.exportToExcel(
|
|
// sheetName: sheetName,
|
|
// data: data,
|
|
// headers: headers,
|
|
// fileName: fileName,
|
|
// keys: keys!,
|
|
// );
|
|
// },
|
|
child: Container(
|
|
padding: const EdgeInsets.all(7.0),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF2E7D6E),
|
|
// color: const Color(0xFF425B5B),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// ?txt!
|
|
// ? Text(
|
|
// 'Export',
|
|
// style: TextStyle(
|
|
// color: Colors.white,
|
|
// fontWeight: FontWeight.w600,
|
|
// fontSize: 14,
|
|
// ),
|
|
// )
|
|
// : null,
|
|
// ?txt! ? SizedBox(width: 15) : null,
|
|
Image.asset(
|
|
"assets/miscellaneous/export.png",
|
|
height: 13,
|
|
width: 13,
|
|
),
|
|
// Icon(Icons.input_sharp, color: Colors.white),
|
|
// Image.asset("assets/miscellaneous/export", height: 15, width: 15),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ExcelExporter {
|
|
/// Export to an Excel file.
|
|
/// - [displayHeaders]: labels to show in Excel (e.g. "S.No.", "Status")
|
|
/// - [keys]: actual field names in your data map
|
|
static Future<void> exportToExcel({
|
|
required String sheetName,
|
|
required List<Map<String, dynamic>> data,
|
|
required List<String> displayHeaders,
|
|
required List<String> keys,
|
|
required String fileName,
|
|
Map<String, ExportValueFormatter>? valueFormatters,
|
|
}) async {
|
|
if (displayHeaders.length != keys.length) {
|
|
throw ArgumentError('displayHeaders and keys must have same length');
|
|
}
|
|
|
|
try {
|
|
final excel = Excel.createExcel();
|
|
|
|
// Rename default sheet if needed
|
|
if (excel.sheets.containsKey('Sheet1')) {
|
|
try {
|
|
excel.rename('Sheet1', sheetName);
|
|
} catch (_) {}
|
|
}
|
|
|
|
// Get the sheet
|
|
final sheet = excel[sheetName]!;
|
|
|
|
// ✅ Add headers as CellValues
|
|
sheet.appendRow(displayHeaders.map((h) => TextCellValue(h)).toList());
|
|
|
|
// ✅ Add data rows
|
|
// for (var i = 0; i < data.length; i++) {
|
|
// final rowMap = data[i];
|
|
// final row = <CellValue?>[];
|
|
//
|
|
// for (var key in keys) {
|
|
// dynamic value;
|
|
// if (key == 'sno' ||
|
|
// key.toLowerCase() == 's.no' ||
|
|
// key.toLowerCase() == 'sno.') {
|
|
// value = i + 1; // serial number
|
|
// } else {
|
|
// value = rowMap[key];
|
|
// }
|
|
//
|
|
// if (value == null) {
|
|
// row.add(TextCellValue('-'));
|
|
// } else if (value is int) {
|
|
// row.add(IntCellValue(value));
|
|
// } else if (value is double) {
|
|
// row.add(DoubleCellValue(value));
|
|
// } else {
|
|
// row.add(TextCellValue(value.toString()));
|
|
// }
|
|
// }
|
|
//
|
|
// sheet.appendRow(row);
|
|
// }
|
|
|
|
// ✅ Reverse the data list so latest entries come first
|
|
final reversedData = data.reversed.toList();
|
|
final formatters = valueFormatters;
|
|
|
|
for (var i = 0; i < reversedData.length; i++) {
|
|
final rowMap = reversedData[i];
|
|
final row = <CellValue?>[];
|
|
|
|
// Inside ExcelExporter class -> exportToExcel method
|
|
for (var key in keys) {
|
|
dynamic value;
|
|
|
|
if (key == 'sno' || key.toLowerCase() == 's.no' || key.toLowerCase() == 'sno.') {
|
|
value = i + 1;
|
|
} else if (formatters != null && formatters.containsKey(key)) {
|
|
final formatted = formatters[key]!(rowMap[key], rowMap);
|
|
final text = formatted.trim().isEmpty ? '-' : formatted;
|
|
row.add(TextCellValue(text));
|
|
continue;
|
|
} else if (key.toLowerCase() == 'is_active') {
|
|
final rawVal = rowMap[key];
|
|
value = (rawVal == 1 || rawVal == '1') ? 'Active' : 'Inactive';
|
|
} else {
|
|
// FIX: Ensure we extract the value and check if it's "null" as a string
|
|
value = rowMap[key];
|
|
if (value == "null") value = null;
|
|
}
|
|
|
|
if (value == null || value.toString().trim().isEmpty) {
|
|
row.add(TextCellValue('-')); // This ensures the column isn't empty
|
|
} else if (value is int) {
|
|
row.add(IntCellValue(value));
|
|
} else if (value is double) {
|
|
row.add(DoubleCellValue(value));
|
|
} else {
|
|
row.add(TextCellValue(value.toString()));
|
|
}
|
|
}
|
|
sheet.appendRow(row);
|
|
}
|
|
|
|
|
|
final excelBytes = excel.encode();
|
|
if (excelBytes == null) throw Exception('Failed to encode Excel file');
|
|
|
|
if (kIsWeb) {
|
|
// Web download
|
|
final base64Str = base64Encode(excelBytes);
|
|
final dataUri = 'data:application/octet-stream;base64,$base64Str';
|
|
final anchor = html.AnchorElement(href: dataUri)
|
|
..setAttribute('download', '$fileName.xlsx')
|
|
..click();
|
|
} else {
|
|
// Mobile/Desktop save + open
|
|
final dir = await getApplicationDocumentsDirectory();
|
|
final filePath = '${dir.path}/$fileName.xlsx';
|
|
final file = File(filePath);
|
|
await file.writeAsBytes(excelBytes, flush: true);
|
|
await OpenFile.open(file.path);
|
|
}
|
|
} catch (e, st) {
|
|
debugPrint('❌ Excel export error: $e\n$st');
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
// class ExcelExporter {
|
|
// static Future<void> exportToExcel({
|
|
// required String sheetName,
|
|
// required List<Map<String, dynamic>> data,
|
|
// required List<String> headers,
|
|
// required String fileName,
|
|
// required List<String> keys,
|
|
// }) async {
|
|
// try {
|
|
// // Create Excel
|
|
// var excel = Excel.createExcel();
|
|
//
|
|
// // Rename default sheet
|
|
// if (excel.sheets.containsKey('Sheet1')) {
|
|
// excel.rename('Sheet1', sheetName);
|
|
// }
|
|
//
|
|
// // Get the sheet
|
|
// Sheet sheetObject = excel[sheetName]!;
|
|
//
|
|
// // Add headers
|
|
// sheetObject.appendRow(keys.map((h) => TextCellValue(h)).toList());
|
|
//
|
|
// // Add data
|
|
// for (var rowData in data) {
|
|
// var row = <CellValue?>[];
|
|
// for (var key in keys) {
|
|
// final value = rowData[key];
|
|
// if (value == null) {
|
|
// row.add(TextCellValue('-'));
|
|
// } else if (value is int) {
|
|
// row.add(IntCellValue(value));
|
|
// } else if (value is double) {
|
|
// row.add(DoubleCellValue(value));
|
|
// } else {
|
|
// row.add(TextCellValue(value.toString()));
|
|
// }
|
|
// }
|
|
// sheetObject.appendRow(row);
|
|
// }
|
|
//
|
|
// var bytes = excel.encode()!;
|
|
//
|
|
// if (kIsWeb) {
|
|
// // ✅ Web: base64 download instead of Blob
|
|
// final base64Str = base64Encode(bytes);
|
|
// final dataUri = "data:application/octet-stream;base64,$base64Str";
|
|
// final anchor = html.AnchorElement(href: dataUri)
|
|
// ..setAttribute("download", "$fileName.xlsx")
|
|
// ..click();
|
|
// } else {
|
|
// // Mobile/Desktop: save file
|
|
// final dir = await getApplicationDocumentsDirectory();
|
|
// final file = File('${dir.path}/$fileName.xlsx');
|
|
// await file.writeAsBytes(bytes);
|
|
// await OpenFile.open(file.path);
|
|
// }
|
|
// } catch (e) {
|
|
// print("Error exporting Excel: $e");
|
|
// }
|
|
// }
|
|
// }
|