Export_header

This commit is contained in:
venbaittech 2025-10-27 13:11:22 +05:30
parent 6c1fdb1c10
commit 626b09e3c9
13 changed files with 488 additions and 315 deletions

View File

@ -571,15 +571,35 @@ class EnquiryListState extends ConsumerState<EnquiryList> {
fileName: "enquiry_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: [
"id",
// headers: [],
displayHeaders: [
"S.No.",
"Received Date & Time",
"Vehicle.No.",
"Insurer",
"Updated Date",
"Status",
"Remarks",
],
keys: [
"sno", // handled internally as i + 1
"created_on",
"updated_on",
"reg_no",
"insurer_name",
"updated_on",
"status",
"remarks",
],
// headers: [
// "id",
// "created_on",
// "updated_on",
// "reg_no",
// "insurer_name",
// "status",
// "remarks",
// ],
),
SizedBox(width: 10),

View File

@ -255,7 +255,18 @@ class claimListState extends ConsumerState<claimList> {
txt: !ResponsiveLayout.isMobile(context)
? true
: false,
headers: [
displayHeaders: [
"S.No.",
"Vehicle.No.",
"Insurer",
"Policy No.",
"Policy Expiry Date",
"Claim Type",
"Claim No.",
"Status",
],
keys: [
"sno", // handled internally as i + 1
"reg_no",
"insurer_name",
"policy_no",

View File

@ -252,7 +252,16 @@ class endosementState extends ConsumerState<endosement> {
txt: !ResponsiveLayout.isMobile(context)
? true
: false,
headers: [
displayHeaders: [
"S.No.",
"Vehicle.No.",
"Insurer",
"Policy No.",
"Endorsement Type",
"Endorsement No.",
],
keys: [
"sno", // handled internally as i + 1
"reg_no",
"insurer_name",
"policy_number",

View File

@ -409,7 +409,12 @@ class AttendanceAllDetailsState extends ConsumerState<AttendanceAllDetails> {
fileName: "staff_attendance_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: ["staff_id", "staff_name", "month", "login_count"],
displayHeaders: ["S.No.", "Staff Name", "Month", "Days"],
keys: [
"sno", // handled internally as i + 1
"staff_name", "month", "login_count",
],
),
],
),

View File

@ -253,6 +253,7 @@ class IndividualAttendanceDetailsState
return (item['date'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['name'] ?? '-').toLowerCase().contains(query.toLowerCase()) ||
(item['login_time'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
@ -321,7 +322,7 @@ class IndividualAttendanceDetailsState
),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Attendance For $staffName",
"Attendance",
style: GoogleFonts.inter(
fontSize: ResponsiveLayout.isMobile(context) ? 14 : 18,
fontWeight: FontWeight.w600,
@ -416,8 +417,17 @@ class IndividualAttendanceDetailsState
fileName: "staff_individual_attendance_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: [
"id",
displayHeaders: [
"S.No.",
"Name",
"Date",
"Login",
"Logout",
"Number Of Hours",
],
keys: [
"sno", // handled internally as i + 1
"name",
"date",
"login_time",
"logout_time",
@ -438,6 +448,7 @@ class IndividualAttendanceDetailsState
child: Row(
children: [
Expanded(flex: 1, child: Text('S.No.', style: _headerStyle)),
Expanded(flex: 2, child: Text('Name', style: _headerStyle)),
Expanded(flex: 2, child: Text('Date', style: _headerStyle)),
Expanded(flex: 2, child: Text('Login', style: _headerStyle)),
@ -502,6 +513,7 @@ class IndividualAttendanceDetailsState
child: Row(
children: [
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)),
Expanded(flex: 2, child: Text(item['date'] ?? '-', style: _dataBold)),
Expanded(

View File

@ -528,6 +528,9 @@ class AgentState extends ConsumerState<Agent> {
SizedBox(width: 10),
ThemedFormField(
controller: controllers['code']!,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
],
validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
),

View File

@ -280,12 +280,21 @@ class AgentListState extends ConsumerState<AgentList> {
? true
: false,
data: filteredData,
headers: [
"id",
displayHeaders: [
'S.No.',
'Partner Name',
'Email',
'Phone Number',
'Id',
'Address',
'Status',
],
keys: [
"sno", // handled internally as i + 1
"name",
"agent_code",
"email",
"mobile",
"agent_code",
"address",
"is_active",
],

View File

@ -255,12 +255,20 @@ class StaffListState extends ConsumerState<StaffList> {
? true
: false,
data: filteredData,
headers: [
"id",
displayHeaders: [
'S.No.',
'Staff Name',
'Role',
'Email',
'Phone Number',
'Status',
],
keys: [
"sno", // handled internally as i + 1
"name",
"role",
"email",
"mobile",
"role",
"is_active",
],
),

View File

@ -1234,7 +1234,7 @@ class othersPendings extends StatelessWidget {
flex: 2,
child: Text(
// stringFlag + " Issued",
'Pending Enquiries',
'Total Assigned',
textAlign: TextAlign.center,
style: _headerStyle,
),
@ -1344,69 +1344,81 @@ class othersPendings extends StatelessWidget {
),
Expanded(
flex: 2,
child: InkWell(
onTap: () {
print(
'Awaiting Proposal : ${row['awaiting_quotation']}',
);
child: Material(
color: Colors.white,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
onTap: () {
print(
'Awaiting Proposal : ${row['awaiting_quotation']}',
);
handleDashboardNavigation(
context,
status: "Awaiting Proposal",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['awaiting_quotation'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
handleDashboardNavigation(
context,
status: "Awaiting Proposal",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['awaiting_quotation'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
),
),
),
),
Expanded(
flex: 2,
child: InkWell(
onTap: () async {
print(
'Awaiting Approval : ${row['pending_quotation_approval']} - ${row['staff_id']}',
);
handleDashboardNavigation(
context,
status: "Proposal Created",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['pending_quotation_approval'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
child: Material(
color: Colors.white,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
onTap: () async {
print(
'Awaiting Approval : ${row['pending_quotation_approval']} - ${row['staff_id']}',
);
handleDashboardNavigation(
context,
status: "Proposal Created",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['pending_quotation_approval'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
),
),
),
),
Expanded(
flex: 2,
child: InkWell(
onTap: () {
print(
'Awaiting Policy : ${row['awaiting_policy']}',
);
child: Material(
color: Colors.white,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
onTap: () {
print(
'Awaiting Policy : ${row['awaiting_policy']}',
);
handleDashboardNavigation(
context,
status: "Proposal Accepted",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['awaiting_policy'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
handleDashboardNavigation(
context,
status: "Proposal Accepted",
staffId: row['staff_id'] ?? '',
role: role,
);
},
child: Text(
row['awaiting_policy'] ?? "",
// row['total_approval_pending'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
),
),
),
),

View File

@ -764,14 +764,27 @@ class EnquiryHandlerState extends ConsumerState<EnquiryHandler> {
fileName: "Enquiry_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: [
displayHeaders: [
"Received Date & Time",
"Partner",
"Assigned To",
"Insurer",
"Vehicle.No.",
"Insured Name",
"Assigned Date & Time",
"Premium",
"Payment Mode",
"Policy Number",
"Status",
],
keys: [
"created_on",
"updated_on",
"reg_no",
"agent_name",
"assigned_to_name",
"insurer_name",
"reg_no",
"insured_name",
"updated_on",
"premium_amount",
"payment_mode",
"policy_number",
@ -887,26 +900,29 @@ class EnquiryHandlerState extends ConsumerState<EnquiryHandler> {
flex: 2,
child: Text('Received Date', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Assigned Date', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Vehicle.No', style: _headerStyle),
),
Expanded(flex: 2, child: Text('Partner', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Assigned To', style: _headerStyle),
),
Expanded(flex: 3, child: Text('Insurer', style: _headerStyle)),
Expanded(
flex: 2,
child: Padding(
padding: EdgeInsets.only(left: 8.0),
child: Text('Insured Name', style: _headerStyle),
),
child: Text('Vehicle.No', style: _headerStyle),
),
Expanded(
flex: 2,
// child: Padding(
// padding: EdgeInsets.only(left: 8.0),
child: Text('Insured Name', style: _headerStyle),
// ),
),
Expanded(
flex: 2,
child: Text('Assigned Date', style: _headerStyle),
),
Expanded(flex: 2, child: Text('Premium', style: _headerStyle)),
Expanded(
@ -1023,19 +1039,7 @@ class EnquiryHandlerState extends ConsumerState<EnquiryHandler> {
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['updated_on']) ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['agent_name'] ?? '-', style: _dataBold),
@ -1053,6 +1057,10 @@ class EnquiryHandlerState extends ConsumerState<EnquiryHandler> {
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
@ -1063,6 +1071,16 @@ class EnquiryHandlerState extends ConsumerState<EnquiryHandler> {
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['updated_on']) ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(

View File

@ -827,17 +827,28 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
fileName: "Enquiry_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: [
displayHeaders: [
"Received Date & Time",
"Partner",
"Assigned To",
"Insurer",
"Vehicle.No.",
"Insured Name",
"Assigned Date & Time",
"Status",
],
keys: [
"created_on",
"updated_on",
"reg_no",
"agent_name",
"assigned_to_name",
"insurer_name",
"reg_no",
"insured_name",
"premium_amount",
"payment_mode",
"policy_number",
// "assigned_to_name",
"updated_on",
// "premium_amount",
// "payment_mode",
// "policy_number",
"status",
],
),
@ -859,6 +870,10 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
child: Text('Received Date & Time', style: _headerStyle),
),
Expanded(flex: 2, child: Text('Partner ', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Assigned To ', style: _headerStyle),
),
Expanded(flex: 4, child: Text('Insurer', style: _headerStyle)),
Expanded(
flex: 3,
@ -955,6 +970,15 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
maxLines: 2,
),
),
Expanded(
flex: 2,
child: Text(
item['assigned_to_name'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 2,
),
),
Expanded(
flex: 4,
child: Text(
@ -968,61 +992,65 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
Expanded(
flex: 3,
child: Builder(
builder: (buttonContext) => InkWell(
hoverColor: const Color(0xffD9EBE8),
focusColor: const Color(0xffD9EBE8),
splashColor: const Color(0xffD9EBE8),
highlightColor: const Color(0xffD9EBE8),
onTap: () async {
// Get overlay and button position RELATIVE to InkWell
final RenderBox button =
buttonContext.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(buttonContext).context.findRenderObject()
as RenderBox;
builder: (buttonContext) => Material(
color: Colors.white,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
// hoverColor: const Color(0xffD9EBE8),
// focusColor: const Color(0xffD9EBE8),
// splashColor: const Color(0xffD9EBE8),
// highlightColor: const Color(0xffD9EBE8),
onTap: () async {
// Get overlay and button position RELATIVE to InkWell
final RenderBox button =
buttonContext.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(buttonContext).context.findRenderObject()
as RenderBox;
final Offset position = button.localToGlobal(
Offset.zero,
ancestor: overlay,
);
final Offset position = button.localToGlobal(
Offset.zero,
ancestor: overlay,
);
await showMenu(
context: buttonContext,
position: RelativeRect.fromLTRB(
position.dx,
position.dy + button.size.height,
overlay.size.width - position.dx,
0,
),
items: [
PopupMenuItem(
child: Column(
mainAxisSize: MainAxisSize.min,
children: _buildPopupDownload(
buttonContext,
item,
item['id'],
item['reg_no'],
await showMenu(
context: buttonContext,
position: RelativeRect.fromLTRB(
position.dx,
position.dy + button.size.height,
overlay.size.width - position.dx,
0,
),
items: [
PopupMenuItem(
child: Column(
mainAxisSize: MainAxisSize.min,
children: _buildPopupDownload(
buttonContext,
item,
item['id'],
item['reg_no'],
),
),
),
),
],
color: Colors.white,
);
},
child: Tooltip(
message:
'Click and Download Enquiry Files', // what appears on hover/long press
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Text(
item['reg_no'] ?? '-',
style: _dataBold,
overflow: TextOverflow
.ellipsis, // optional, if text might overflow
],
color: Colors.white,
);
},
child: Tooltip(
message:
'Click and Download Enquiry Files', // what appears on hover/long press
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Text(
item['reg_no'] ?? '-',
style: _dataBold,
overflow: TextOverflow
.ellipsis, // optional, if text might overflow
),
),
// child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
// child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
),
),
@ -1049,32 +1077,36 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
),
Expanded(
flex: 3,
child: InkWell(
onTap: () async {
var status = item['status'];
var enqId = item['id'];
child: Material(
color: Colors.white,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
onTap: () async {
var status = item['status'];
var enqId = item['id'];
final prefs = await SharedPreferences.getInstance();
final prefs = await SharedPreferences.getInstance();
// Remove old value (if any)
await prefs.remove('enqStaffDataId');
// Remove old value (if any)
await prefs.remove('enqStaffDataId');
// Save the new id
await prefs.setString('enqStaffDataId', enqId.toString());
ref.read(quotationStaffIdProvider.notifier).state = enqId;
buildStatusActions(context, status, enqId);
},
child: Tooltip(
message: 'Click to View or Process Enquiry', //
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Text(
item['status'] ?? '-',
style: _dataBold,
overflow: TextOverflow.ellipsis, //
// Save the new id
await prefs.setString('enqStaffDataId', enqId.toString());
ref.read(quotationStaffIdProvider.notifier).state = enqId;
buildStatusActions(context, status, enqId);
},
child: Tooltip(
message: 'Click to View or Process Enquiry', //
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Text(
item['status'] ?? '-',
style: _dataBold,
overflow: TextOverflow.ellipsis, //
),
),
// child: Text(item['status'] ?? '-', style: _dataBold),
),
// child: Text(item['status'] ?? '-', style: _dataBold),
),
),
],

View File

@ -588,15 +588,26 @@ class policylistState extends ConsumerState<policylist> {
fileName: "Policy_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: [
"reg_no",
displayHeaders: [
"Received Date & Time",
"Partner",
"Assigned To",
"Insurer",
"Vehicle.No.",
"Insured Name",
"Premium",
"Payment Mode",
"Policy Number",
],
keys: [
"updated_on",
"agent_name",
"assigned_to_name",
"insurer_name",
"reg_no",
"insured_name",
"premium_amount",
"payment_mode",
"updated_on",
"policy_number",
],
),

View File

@ -3,6 +3,7 @@ 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';
@ -21,91 +22,171 @@ class ExportBtn extends HookWidget {
final String sheetName;
final String fileName;
final List<Map<String, dynamic>> data;
final List<String> headers; // dynamic headers
final List<String>? displayHeaders; // dynamic headers
// final List<String> headers; // dynamic headers
final List<String>? keys; // dynamic headers
final bool? txt;
const ExportBtn({
super.key,
this.txt,
required this.sheetName,
required this.fileName,
required this.data,
required this.headers,
// required this.headers,
required this.displayHeaders,
this.keys,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
ExcelExporter.exportToExcel(
sheetName: sheetName,
data: data,
headers: headers,
fileName: fileName,
);
},
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
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: 20) : null,
Image.asset(
"assets/miscellaneous/export.png",
height: 25,
width: 25,
),
// Icon(Icons.input_sharp, color: Colors.white),
// Image.asset("assets/miscellaneous/export", height: 15, width: 15),
],
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,
);
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(8.0),
decoration: BoxDecoration(
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: 20) : null,
Image.asset(
"assets/miscellaneous/export.png",
height: 25,
width: 25,
),
// 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> headers,
required List<String> displayHeaders,
required List<String> keys,
required String fileName,
}) async {
try {
// Create Excel
var excel = Excel.createExcel();
if (displayHeaders.length != keys.length) {
throw ArgumentError('displayHeaders and keys must have same length');
}
// Rename default sheet
try {
final excel = Excel.createExcel();
// Rename default sheet if needed
if (excel.sheets.containsKey('Sheet1')) {
excel.rename('Sheet1', sheetName);
try {
excel.rename('Sheet1', sheetName);
} catch (_) {}
}
// Get the sheet
Sheet sheetObject = excel[sheetName]!;
final sheet = excel[sheetName]!;
// Add headers
sheetObject.appendRow(headers.map((h) => TextCellValue(h)).toList());
// 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();
for (var i = 0; i < reversedData.length; i++) {
final rowMap = reversedData[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 (descending)
} else if (key.toLowerCase() == 'is_active') {
// Handle Active/Inactive display
final rawVal = rowMap[key];
value = (rawVal == 1 || rawVal == '1') ? 'Active' : 'Inactive';
} else {
value = rowMap[key];
}
// Add data
for (var rowData in data) {
var row = <CellValue?>[];
for (var key in headers) {
final value = rowData[key];
if (value == null) {
row.add(TextCellValue('-'));
} else if (value is int) {
@ -116,56 +197,62 @@ class ExcelExporter {
row.add(TextCellValue(value.toString()));
}
}
sheetObject.appendRow(row);
sheet.appendRow(row);
}
var bytes = excel.encode()!;
final excelBytes = excel.encode();
if (excelBytes == null) throw Exception('Failed to encode Excel file');
if (kIsWeb) {
// Web: base64 download instead of Blob
final base64Str = base64Encode(bytes);
final dataUri = "data:application/octet-stream;base64,$base64Str";
// Web download
final base64Str = base64Encode(excelBytes);
final dataUri = 'data:application/octet-stream;base64,$base64Str';
final anchor = html.AnchorElement(href: dataUri)
..setAttribute("download", "$fileName.xlsx")
..setAttribute('download', '$fileName.xlsx')
..click();
} else {
// Mobile/Desktop: save file
// Mobile/Desktop save + open
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/$fileName.xlsx');
await file.writeAsBytes(bytes);
final filePath = '${dir.path}/$fileName.xlsx';
final file = File(filePath);
await file.writeAsBytes(excelBytes, flush: true);
await OpenFile.open(file.path);
}
} catch (e) {
print("Error exporting Excel: $e");
} 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 and delete default sheet
// // Create Excel
// var excel = Excel.createExcel();
//
// // Rename default sheet
// if (excel.sheets.containsKey('Sheet1')) {
// excel.delete('Sheet1');
// excel.rename('Sheet1', sheetName);
// }
//
// // Create your sheet
// Sheet sheetObject = excel[sheetName]; // now this is the only sheet
// // Get the sheet
// Sheet sheetObject = excel[sheetName]!;
//
// // Add headers
// sheetObject.appendRow(headers.map((h) => TextCellValue(h)).toList());
// sheetObject.appendRow(keys.map((h) => TextCellValue(h)).toList());
//
// // Add data
// for (var rowData in data) {
// var row = <CellValue?>[];
// for (var key in headers) {
// for (var key in keys) {
// final value = rowData[key];
// if (value == null) {
// row.add(TextCellValue('-'));
@ -183,13 +270,12 @@ class ExcelExporter {
// var bytes = excel.encode()!;
//
// if (kIsWeb) {
// // Web: download
// final blob = html.Blob([bytes]);
// final url = html.Url.createObjectUrlFromBlob(blob);
// final anchor = html.AnchorElement(href: url)
// // 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();
// html.Url.revokeObjectUrl(url);
// } else {
// // Mobile/Desktop: save file
// final dir = await getApplicationDocumentsDirectory();
@ -202,66 +288,3 @@ class ExcelExporter {
// }
// }
// }
//---------------------------------------------------------------------------
// class ExcelExporter {
// static Future<void> exportToExcel({
// required String sheetName,
// required List<Map<String, dynamic>> data,
// required List<String> headers,
// required String fileName,
// }) async {
// try {
// // var excel = Excel.createExcel();
// // Sheet sheetObject = excel[sheetName];
//
// // Delete default sheet
// var excel = Excel.createExcel();
// excel.delete('Sheet1'); // remove default
// Sheet sheetObject = excel[sheetName];
//
// // Add headers
// sheetObject.appendRow(headers.map((h) => TextCellValue(h)).toList());
//
// // Add data
// for (int i = 0; i < data.length; i++) {
// var row = <CellValue?>[];
// for (var key in headers) {
// final value = data[i][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: download as file
// final blob = html.Blob([bytes]);
// final url = html.Url.createObjectUrlFromBlob(blob);
// final anchor = html.AnchorElement(href: url)
// ..setAttribute("download", "$fileName.xlsx")
// ..click();
// html.Url.revokeObjectUrl(url);
// } else {
// // Mobile/Desktop: save to file system
// 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");
// }
// }
// }