596 lines
22 KiB
Dart
596 lines
22 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:fbla_ui/shared/api_logic.dart';
|
|
import 'package:fbla_ui/shared/utils.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:open_filex/open_filex.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
import 'package:printing/printing.dart';
|
|
|
|
Map<DataTypeBusiness, int> dataTypePriorityBusiness = {
|
|
DataTypeBusiness.logo: 0,
|
|
DataTypeBusiness.name: 1,
|
|
DataTypeBusiness.description: 2,
|
|
DataTypeBusiness.type: 3,
|
|
DataTypeBusiness.website: 4,
|
|
DataTypeBusiness.contactName: 5,
|
|
DataTypeBusiness.contactEmail: 6,
|
|
DataTypeBusiness.contactPhone: 7,
|
|
DataTypeBusiness.notes: 8
|
|
};
|
|
|
|
Map<DataTypeBusiness, String> dataTypeFriendlyBusiness = {
|
|
DataTypeBusiness.logo: 'Logo',
|
|
DataTypeBusiness.name: 'Name',
|
|
DataTypeBusiness.description: 'Description',
|
|
DataTypeBusiness.type: 'Type',
|
|
DataTypeBusiness.website: 'Website',
|
|
DataTypeBusiness.contactName: 'Contact Name',
|
|
DataTypeBusiness.contactEmail: 'Contact Email',
|
|
DataTypeBusiness.contactPhone: 'Contact Phone',
|
|
DataTypeBusiness.notes: 'Notes'
|
|
};
|
|
|
|
Map<DataTypeJob, int> dataTypePriorityJob = {
|
|
DataTypeJob.businessName: 1,
|
|
DataTypeJob.name: 2,
|
|
DataTypeJob.description: 3,
|
|
DataTypeJob.type: 4,
|
|
DataTypeJob.offerType: 5,
|
|
DataTypeJob.wage: 6,
|
|
DataTypeJob.link: 7,
|
|
};
|
|
|
|
Map<DataTypeJob, String> dataTypeFriendlyJob = {
|
|
DataTypeJob.businessName: 'Business Name',
|
|
DataTypeJob.name: 'Job Listing Name',
|
|
DataTypeJob.description: 'Description',
|
|
DataTypeJob.type: 'Job Type',
|
|
DataTypeJob.offerType: 'Offer Type',
|
|
DataTypeJob.wage: 'Wage Information',
|
|
DataTypeJob.link: 'Additional Info Link',
|
|
};
|
|
|
|
Set<DataTypeBusiness> sortDataTypesBusiness(Set<DataTypeBusiness> set) {
|
|
List<DataTypeBusiness> list = set.toList();
|
|
list.sort((a, b) {
|
|
return dataTypePriorityBusiness[a]!.compareTo(dataTypePriorityBusiness[b]!);
|
|
});
|
|
set = list.toSet();
|
|
return set;
|
|
}
|
|
|
|
Set<DataTypeJob> sortDataTypesJob(Set<DataTypeJob> set) {
|
|
List<DataTypeJob> list = set.toList();
|
|
list.sort((a, b) {
|
|
return dataTypePriorityJob[a]!.compareTo(dataTypePriorityJob[b]!);
|
|
});
|
|
set = list.toSet();
|
|
return set;
|
|
}
|
|
|
|
class _FilterBusinessDataTypeChips extends StatefulWidget {
|
|
final Set<DataTypeBusiness> selectedDataTypesBusiness;
|
|
|
|
const _FilterBusinessDataTypeChips({required this.selectedDataTypesBusiness});
|
|
|
|
@override
|
|
State<_FilterBusinessDataTypeChips> createState() =>
|
|
_FilterBusinessDataTypeChipsState();
|
|
}
|
|
|
|
class _FilterBusinessDataTypeChipsState
|
|
extends State<_FilterBusinessDataTypeChips> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<Widget> chips = [];
|
|
for (var type in DataTypeBusiness.values) {
|
|
chips.add(FilterChip(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side: BorderSide(color: Theme.of(context).colorScheme.secondary)),
|
|
label: Text(dataTypeFriendlyBusiness[type]!),
|
|
showCheckmark: false,
|
|
selected: widget.selectedDataTypesBusiness.contains(type),
|
|
onSelected: (bool selected) {
|
|
setState(() {
|
|
if (selected) {
|
|
widget.selectedDataTypesBusiness.add(type);
|
|
} else {
|
|
widget.selectedDataTypesBusiness.remove(type);
|
|
}
|
|
});
|
|
}));
|
|
}
|
|
return Wrap(
|
|
spacing: 6,
|
|
runSpacing: 6,
|
|
children: chips,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FilterJobDataTypeChips extends StatefulWidget {
|
|
final Set<DataTypeJob> selectedDataTypesJob;
|
|
|
|
const _FilterJobDataTypeChips({required this.selectedDataTypesJob});
|
|
|
|
@override
|
|
State<_FilterJobDataTypeChips> createState() =>
|
|
_FilterJobDataTypeChipsState();
|
|
}
|
|
|
|
class _FilterJobDataTypeChipsState extends State<_FilterJobDataTypeChips> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<Padding> chips = [];
|
|
for (var type in DataTypeJob.values) {
|
|
chips.add(Padding(
|
|
padding:
|
|
const EdgeInsets.only(left: 3.0, right: 3.0, bottom: 3.0, top: 3.0),
|
|
child: FilterChip(
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side:
|
|
BorderSide(color: Theme.of(context).colorScheme.secondary)),
|
|
label: Text(dataTypeFriendlyJob[type]!),
|
|
showCheckmark: false,
|
|
selected: widget.selectedDataTypesJob.contains(type),
|
|
onSelected: (bool selected) {
|
|
setState(() {
|
|
if (selected) {
|
|
widget.selectedDataTypesJob.add(type);
|
|
} else {
|
|
widget.selectedDataTypesJob.remove(type);
|
|
}
|
|
});
|
|
}),
|
|
));
|
|
}
|
|
return Wrap(
|
|
spacing: 6,
|
|
runSpacing: 6,
|
|
children: chips,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> generatePDF(
|
|
{required BuildContext context,
|
|
required int documentTypeIndex,
|
|
Set<Business>? selectedBusinesses,
|
|
Set<Business>? selectedJobs}) async {
|
|
List<pw.Widget> headerColumns = [];
|
|
List<pw.TableRow> tableRows = [];
|
|
|
|
Set<DataTypeBusiness> dataTypesBusiness = {};
|
|
Set<DataTypeJob> dataTypesJob = {};
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
contentPadding: const EdgeInsets.all(16),
|
|
scrollable: true,
|
|
title: const Text('Export Settings'),
|
|
content: SizedBox(
|
|
width: 400,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Text('Data columns you want to export:'),
|
|
),
|
|
documentTypeIndex == 0
|
|
? _FilterBusinessDataTypeChips(
|
|
selectedDataTypesBusiness: dataTypesBusiness,
|
|
)
|
|
: _FilterJobDataTypeChips(
|
|
selectedDataTypesJob: dataTypesJob)
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
child: const Text('Cancel'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
}),
|
|
TextButton(
|
|
child: const Text('Generate'),
|
|
onPressed: () async {
|
|
if (documentTypeIndex == 0) {
|
|
List<Business> businesses = await fetchBusinesses(
|
|
selectedBusinesses!
|
|
.map((business) => business.id)
|
|
.toList());
|
|
if (dataTypesBusiness.isEmpty) {
|
|
dataTypesBusiness.addAll(DataTypeBusiness.values);
|
|
}
|
|
dataTypesBusiness =
|
|
sortDataTypesBusiness(dataTypesBusiness);
|
|
|
|
for (Business business in businesses) {
|
|
List<pw.Widget> businessRow = [];
|
|
if (dataTypesBusiness.contains(DataTypeBusiness.logo)) {
|
|
var apiLogo = await getLogo(business.id);
|
|
if (apiLogo.runtimeType != String) {
|
|
businessRow.add(pw.Padding(
|
|
child: pw.ClipRRect(
|
|
child: pw.Image(pw.MemoryImage(apiLogo),
|
|
height: 24, width: 24),
|
|
horizontalRadius: 4,
|
|
verticalRadius: 4),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
} else {
|
|
businessRow.add(pw.Padding(
|
|
child: pw.Icon(
|
|
getPwIconFromBusinessType(business.type!),
|
|
size: 24),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
}
|
|
for (DataTypeBusiness dataType in dataTypesBusiness) {
|
|
if (dataType != DataTypeBusiness.logo) {
|
|
var currentValue =
|
|
businessValueFromDataType(business, dataType);
|
|
if (currentValue != null) {
|
|
businessRow.add(pw.Padding(
|
|
child: pw.Text(businessValueFromDataType(
|
|
business, dataType)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
} else {
|
|
businessRow.add(pw.Container());
|
|
}
|
|
}
|
|
}
|
|
tableRows.add(pw.TableRow(children: businessRow));
|
|
}
|
|
|
|
for (var filter in dataTypesBusiness) {
|
|
headerColumns.add(pw.Padding(
|
|
child: pw.Text(dataTypeFriendlyBusiness[filter]!,
|
|
style: const pw.TextStyle(fontSize: 10)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
} else {
|
|
if (dataTypesJob.isEmpty) {
|
|
dataTypesJob.addAll(DataTypeJob.values);
|
|
}
|
|
dataTypesJob = sortDataTypesJob(dataTypesJob);
|
|
|
|
// List<Map<String, dynamic>> nameMapping =
|
|
// await fetchBusinessNames();
|
|
|
|
for (Business business in selectedJobs!) {
|
|
for (JobListing job in business.listings!) {
|
|
List<pw.Widget> jobRow = [];
|
|
for (DataTypeJob dataType in dataTypesJob) {
|
|
switch (dataType) {
|
|
case DataTypeJob.businessName:
|
|
jobRow.add(pw.Padding(
|
|
child: pw.Text(business.name!),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
case DataTypeJob.type:
|
|
jobRow.add(pw.Padding(
|
|
child: pw.Text(getNameFromJobType(job.type!)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
case DataTypeJob.offerType:
|
|
jobRow.add(pw.Padding(
|
|
child: pw.Text(
|
|
getNameFromOfferType(job.offerType!)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
default:
|
|
jobRow.add(pw.Padding(
|
|
child: pw.Text(
|
|
jobValueFromDataType(job, dataType) ??
|
|
''),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
// if (dataType != DataTypeJob.businessName) {
|
|
// var currentValue =
|
|
// jobValueFromDataType(job, dataType);
|
|
// if (currentValue != null) {
|
|
// jobRow.add(pw.Padding(
|
|
// child: pw.Text(currentValue),
|
|
// padding: const pw.EdgeInsets.all(4.0)));
|
|
// } else {
|
|
// jobRow.add(pw.Container());
|
|
// }
|
|
// } else {
|
|
// jobRow.add(pw.Padding(
|
|
// child: pw.Text(business.name!),
|
|
// padding: const pw.EdgeInsets.all(4.0)));
|
|
// }
|
|
}
|
|
tableRows.add(pw.TableRow(children: jobRow));
|
|
}
|
|
}
|
|
|
|
for (var filter in dataTypesJob) {
|
|
headerColumns.add(pw.Padding(
|
|
child: pw.Text(dataTypeFriendlyJob[filter]!,
|
|
style: const pw.TextStyle(fontSize: 10)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
}
|
|
// Final Generation
|
|
DateTime dateTime = DateTime.now();
|
|
String minute = '00';
|
|
if (dateTime.minute.toString().length < 2) {
|
|
minute = '0${dateTime.minute}';
|
|
} else {
|
|
minute = dateTime.minute.toString();
|
|
}
|
|
|
|
String time = dateTime.hour <= 12
|
|
? '${dateTime.hour}:${minute}AM'
|
|
: '${dateTime.hour - 12}:${minute}PM';
|
|
String fileName =
|
|
'${documentTypeIndex == 0 ? 'Business' : 'Job Listing'} Data - ${dateTime.month}-${dateTime.day}-${dateTime.year} $time.pdf';
|
|
|
|
final pdf = pw.Document();
|
|
var svg = await rootBundle.loadString('assets/MarinoDev.svg');
|
|
|
|
var themeIcon = pw.ThemeData.withFont(
|
|
base: await PdfGoogleFonts.notoSansDisplayMedium(),
|
|
icons: await PdfGoogleFonts.materialIcons());
|
|
|
|
var finalTheme = themeIcon.copyWith(
|
|
defaultTextStyle: const pw.TextStyle(fontSize: 9),
|
|
);
|
|
|
|
pdf.addPage(pw.MultiPage(
|
|
theme: finalTheme,
|
|
pageFormat: PdfPageFormat.letter,
|
|
orientation: pw.PageOrientation.landscape,
|
|
margin: const pw.EdgeInsets.all(24),
|
|
build: (pw.Context context) {
|
|
return [
|
|
pw.Row(
|
|
mainAxisAlignment:
|
|
pw.MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
pw.SvgImage(svg: svg, height: 40),
|
|
pw.Padding(
|
|
padding: const pw.EdgeInsets.all(8.0),
|
|
child: pw.Text(
|
|
'${documentTypeIndex == 0 ? 'Business' : 'Job Listing'} Datasheet',
|
|
style: pw.TextStyle(
|
|
fontSize: 32,
|
|
fontWeight: pw.FontWeight.bold)),
|
|
),
|
|
pw.Text(
|
|
'Generated on ${dateTime.month}/${dateTime.day}/${dateTime.year} at $time',
|
|
style: const pw.TextStyle(fontSize: 12),
|
|
textAlign: pw.TextAlign.right),
|
|
//
|
|
]),
|
|
pw.Table(
|
|
columnWidths: documentTypeIndex == 0
|
|
? _businessColumnSizes(dataTypesBusiness)
|
|
: _jobColumnSizes(dataTypesJob),
|
|
border: const pw.TableBorder(
|
|
bottom: pw.BorderSide(),
|
|
left: pw.BorderSide(),
|
|
right: pw.BorderSide(),
|
|
top: pw.BorderSide(),
|
|
horizontalInside: pw.BorderSide(),
|
|
verticalInside: pw.BorderSide()),
|
|
children: [
|
|
pw.TableRow(
|
|
decoration: const pw.BoxDecoration(
|
|
color: PdfColors.blue400),
|
|
children: headerColumns,
|
|
repeat: true,
|
|
),
|
|
...tableRows,
|
|
])
|
|
];
|
|
}));
|
|
|
|
Uint8List pdfBytes = await pdf.save();
|
|
|
|
if (kIsWeb) {
|
|
await Printing.sharePdf(
|
|
bytes: await pdf.save(),
|
|
filename: fileName,
|
|
);
|
|
} else {
|
|
var dir = await getTemporaryDirectory();
|
|
var tempDir = dir.path;
|
|
|
|
File pdfFile = File('$tempDir/$fileName');
|
|
pdfFile.writeAsBytesSync(pdfBytes);
|
|
|
|
OpenFilex.open(pdfFile.path);
|
|
}
|
|
Navigator.of(context).pop();
|
|
}),
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
Map<int, pw.TableColumnWidth> _businessColumnSizes(
|
|
Set<DataTypeBusiness> dataTypes) {
|
|
double space = 744.0;
|
|
List<DataTypeBusiness> sorted = sortDataTypesBusiness(dataTypes).toList();
|
|
Map<int, pw.TableColumnWidth> map = {};
|
|
|
|
if (sorted.contains(DataTypeBusiness.logo)) {
|
|
space -= 32;
|
|
map.addAll(
|
|
{sorted.indexOf(DataTypeBusiness.logo): const pw.FixedColumnWidth(32)});
|
|
}
|
|
if (sorted.contains(DataTypeBusiness.type)) {
|
|
space -= 68;
|
|
map.addAll(
|
|
{sorted.indexOf(DataTypeBusiness.type): const pw.FixedColumnWidth(68)});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactName)) {
|
|
space -= 72;
|
|
map.addAll({
|
|
sorted.indexOf(DataTypeBusiness.contactName):
|
|
const pw.FixedColumnWidth(72)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactPhone)) {
|
|
space -= 76;
|
|
map.addAll({
|
|
sorted.indexOf(DataTypeBusiness.contactPhone):
|
|
const pw.FixedColumnWidth(76)
|
|
});
|
|
}
|
|
double leftNum = 0;
|
|
if (dataTypes.contains(DataTypeBusiness.name)) {
|
|
leftNum += 1;
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.website)) {
|
|
leftNum += 1;
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactEmail)) {
|
|
leftNum += 1;
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.notes)) {
|
|
leftNum += 1;
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.description)) {
|
|
leftNum += 3;
|
|
}
|
|
leftNum = space / leftNum;
|
|
if (dataTypes.contains(DataTypeBusiness.name)) {
|
|
map.addAll(
|
|
{sorted.indexOf(DataTypeBusiness.name): pw.FixedColumnWidth(leftNum)});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.website)) {
|
|
map.addAll({
|
|
sorted.indexOf(DataTypeBusiness.website): pw.FixedColumnWidth(leftNum)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactEmail)) {
|
|
map.addAll({
|
|
sorted.indexOf(DataTypeBusiness.contactEmail):
|
|
pw.FixedColumnWidth(leftNum)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.notes)) {
|
|
map.addAll(
|
|
{sorted.indexOf(DataTypeBusiness.notes): pw.FixedColumnWidth(leftNum)});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.description)) {
|
|
map.addAll({
|
|
sorted.indexOf(DataTypeBusiness.description):
|
|
pw.FixedColumnWidth(leftNum * 3)
|
|
});
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
Map<int, pw.TableColumnWidth> _jobColumnSizes(Set<DataTypeJob> dataTypes) {
|
|
Map<int, pw.TableColumnWidth> map = {};
|
|
List<DataTypeJob> sortedDataTypes = sortDataTypesJob(dataTypes).toList();
|
|
|
|
if (dataTypes.contains(DataTypeJob.businessName)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.businessName)
|
|
.first): const pw.FractionColumnWidth(0.2)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.type)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.type)
|
|
.first): const pw.FractionColumnWidth(0.1)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.name)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.name)
|
|
.first): const pw.FractionColumnWidth(0.2)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.description)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.description)
|
|
.first): const pw.FractionColumnWidth(0.3)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.offerType)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.offerType)
|
|
.first): const pw.FractionColumnWidth(0.1)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.wage)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.wage)
|
|
.first): const pw.FractionColumnWidth(0.15)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeJob.link)) {
|
|
map.addAll({
|
|
sortedDataTypes.indexOf(sortedDataTypes
|
|
.where((element) => element == DataTypeJob.link)
|
|
.first): const pw.FractionColumnWidth(0.2)
|
|
});
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
dynamic businessValueFromDataType(
|
|
Business business, DataTypeBusiness dataType) {
|
|
switch (dataType) {
|
|
case DataTypeBusiness.name:
|
|
return business.name;
|
|
case DataTypeBusiness.description:
|
|
return business.description;
|
|
case DataTypeBusiness.type:
|
|
return getNameFromBusinessType(business.type!);
|
|
case DataTypeBusiness.website:
|
|
return business.website;
|
|
case DataTypeBusiness.contactName:
|
|
return business.contactName;
|
|
case DataTypeBusiness.contactEmail:
|
|
return business.contactEmail;
|
|
case DataTypeBusiness.contactPhone:
|
|
return business.contactPhone;
|
|
case DataTypeBusiness.notes:
|
|
return business.notes;
|
|
case DataTypeBusiness.logo:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
dynamic jobValueFromDataType(JobListing job, DataTypeJob dataType) {
|
|
switch (dataType) {
|
|
case DataTypeJob.name:
|
|
return job.name;
|
|
case DataTypeJob.description:
|
|
return job.description;
|
|
case DataTypeJob.type:
|
|
return job.type;
|
|
case DataTypeJob.offerType:
|
|
return job.offerType;
|
|
case DataTypeJob.wage:
|
|
return job.wage;
|
|
case DataTypeJob.link:
|
|
return job.link;
|
|
case DataTypeJob.businessName:
|
|
return null;
|
|
}
|
|
}
|