851 lines
29 KiB
Dart
851 lines
29 KiB
Dart
// import 'dart:html' as html;
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:fbla_ui/api_logic.dart';
|
|
import 'package:fbla_ui/shared.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.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';
|
|
import 'package:rive/rive.dart';
|
|
|
|
class ExportData extends StatefulWidget {
|
|
final Map<JobType, List<Business>> groupedBusinesses;
|
|
|
|
const ExportData({super.key, required this.groupedBusinesses});
|
|
|
|
@override
|
|
State<ExportData> createState() => _ExportDataState();
|
|
}
|
|
|
|
class _ExportDataState extends State<ExportData> {
|
|
String documentType = 'Business';
|
|
late Future refreshBusinessDataFuture;
|
|
bool _isPreviousData = false;
|
|
late Map<JobType, List<Business>> overviewBusinesses;
|
|
Set<JobType> jobTypeFilters = <JobType>{};
|
|
String searchQuery = '';
|
|
Set<DataTypeJob> selectedDataTypesJob = <DataTypeJob>{};
|
|
Set<DataTypeBusiness> selectedDataTypesBusiness = <DataTypeBusiness>{};
|
|
|
|
Future<void> _setFilters(Set<JobType> filters) async {
|
|
setState(() {
|
|
jobTypeFilters = filters;
|
|
});
|
|
_updateOverviewBusinesses();
|
|
}
|
|
|
|
Future<void> _updateOverviewBusinesses() async {
|
|
var refreshedData =
|
|
fetchBusinessDataOverview(typeFilters: jobTypeFilters.toList());
|
|
await refreshedData;
|
|
setState(() {
|
|
refreshBusinessDataFuture = refreshedData;
|
|
});
|
|
}
|
|
|
|
Future<void> _setSearch(String search) async {
|
|
setState(() {
|
|
searchQuery = search;
|
|
});
|
|
_updateOverviewBusinesses();
|
|
}
|
|
|
|
Map<JobType, List<Business>> _filterBySearch(
|
|
Map<JobType, List<Business>> businesses) {
|
|
Map<JobType, List<Business>> filteredBusinesses = businesses;
|
|
|
|
for (JobType jobType in businesses.keys) {
|
|
filteredBusinesses[jobType]!.removeWhere((tmpBusiness) => !tmpBusiness
|
|
.name
|
|
.replaceAll(RegExp(r'[^a-zA-Z]'), '')
|
|
.toLowerCase()
|
|
.contains(searchQuery
|
|
.replaceAll(RegExp(r'[^a-zA-Z]'), '')
|
|
.toLowerCase()
|
|
.trim()));
|
|
}
|
|
filteredBusinesses.removeWhere((key, value) => value.isEmpty);
|
|
return filteredBusinesses;
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
refreshBusinessDataFuture = fetchBusinessDataOverview();
|
|
selectedBusinesses = <Business>{};
|
|
}
|
|
|
|
void _setStateCallbackReset() {
|
|
setState(() {
|
|
selectedDataTypesBusiness = <DataTypeBusiness>{};
|
|
selectedDataTypesJob = <DataTypeJob>{};
|
|
documentType = 'Business';
|
|
});
|
|
}
|
|
|
|
void _setStateCallbackApply(String docType, Set<DataTypeJob> dataFiltersJob,
|
|
Set<DataTypeBusiness> dataFiltersBusiness) {
|
|
setState(() {
|
|
selectedDataTypesBusiness = dataFiltersBusiness;
|
|
selectedDataTypesJob = dataFiltersJob;
|
|
documentType = docType;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool widescreen = MediaQuery.sizeOf(context).width >= 1000;
|
|
return Scaffold(
|
|
floatingActionButton: _FAB(
|
|
groupedBusinesses: widget.groupedBusinesses,
|
|
documentType: documentType,
|
|
selectedDataTypesBusiness: selectedDataTypesBusiness,
|
|
selectedDataTypesJob: selectedDataTypesJob,
|
|
),
|
|
body: CustomScrollView(
|
|
slivers: [
|
|
SliverAppBar(
|
|
forceMaterialTransparency: false,
|
|
title: const Text('Export Data'),
|
|
toolbarHeight: 70,
|
|
pinned: true,
|
|
centerTitle: true,
|
|
expandedHeight: 120,
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
Set<DataTypeBusiness> dataFiltersBusinessTmp =
|
|
Set<DataTypeBusiness>.from(
|
|
selectedDataTypesBusiness);
|
|
Set<DataTypeJob> dataFiltersJobTmp =
|
|
Set<DataTypeJob>.from(selectedDataTypesJob);
|
|
String docTypeTmp = documentType;
|
|
return StatefulBuilder(builder: (context, setState) {
|
|
void segmentedCallback(String docType) {
|
|
setState(() {
|
|
docTypeTmp = docType;
|
|
});
|
|
}
|
|
|
|
void chipsCallback(
|
|
{Set<DataTypeJob>? selectedDataTypesJob,
|
|
Set<DataTypeBusiness>?
|
|
selectedDataTypesBusiness}) {
|
|
if (selectedDataTypesJob != null) {
|
|
dataFiltersJobTmp = selectedDataTypesJob;
|
|
}
|
|
if (selectedDataTypesBusiness != null) {
|
|
dataFiltersBusinessTmp =
|
|
selectedDataTypesBusiness;
|
|
}
|
|
}
|
|
|
|
return AlertDialog(
|
|
// DO NOT MOVE TO SEPARATE WIDGET, setState is needed in main tree
|
|
backgroundColor:
|
|
Theme.of(context).colorScheme.surface,
|
|
title: const Text('Export Settings'),
|
|
content: SizedBox(
|
|
width: 450,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text('Document Type:'),
|
|
_SegmentedButton(
|
|
callback: segmentedCallback,
|
|
docType: docTypeTmp,
|
|
),
|
|
const Text(
|
|
'Data Columns you would like to show on the datasheet:'),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: _FilterDataTypeChips(
|
|
docTypeTmp,
|
|
dataFiltersJobTmp,
|
|
dataFiltersBusinessTmp,
|
|
chipsCallback),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
child: const Text('Reset'),
|
|
onPressed: () {
|
|
_setStateCallbackReset();
|
|
Navigator.of(context).pop();
|
|
}),
|
|
TextButton(
|
|
child: const Text('Cancel'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
}),
|
|
TextButton(
|
|
child: const Text('Apply'),
|
|
onPressed: () {
|
|
_setStateCallbackApply(
|
|
docTypeTmp,
|
|
dataFiltersJobTmp,
|
|
dataFiltersBusinessTmp);
|
|
Navigator.of(context).pop();
|
|
}),
|
|
],
|
|
);
|
|
});
|
|
});
|
|
},
|
|
),
|
|
],
|
|
bottom: PreferredSize(
|
|
preferredSize: const Size.fromHeight(0),
|
|
child: SizedBox(
|
|
height: 70,
|
|
width: 1000,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(10),
|
|
child: BusinessSearchBar(
|
|
filters: jobTypeFilters,
|
|
setFiltersCallback: _setFilters,
|
|
setSearchCallback: _setSearch),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
FutureBuilder(
|
|
future: refreshBusinessDataFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.done) {
|
|
if (snapshot.hasData) {
|
|
if (snapshot.data.runtimeType == String) {
|
|
_isPreviousData = false;
|
|
return SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(children: [
|
|
Center(
|
|
child: Text(snapshot.data,
|
|
textAlign: TextAlign.center)),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: FilledButton(
|
|
child: const Text('Retry'),
|
|
onPressed: () {
|
|
_updateOverviewBusinesses();
|
|
},
|
|
),
|
|
),
|
|
]),
|
|
));
|
|
}
|
|
|
|
overviewBusinesses = snapshot.data;
|
|
_isPreviousData = true;
|
|
|
|
return BusinessDisplayPanel(
|
|
groupedBusinesses: _filterBySearch(overviewBusinesses),
|
|
widescreen: widescreen,
|
|
selectable: true);
|
|
} else if (snapshot.hasError) {
|
|
return SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
|
child: Text(
|
|
'Error when loading data! Error: ${snapshot.error}'),
|
|
));
|
|
}
|
|
} else if (snapshot.connectionState ==
|
|
ConnectionState.waiting) {
|
|
if (_isPreviousData) {
|
|
return BusinessDisplayPanel(
|
|
groupedBusinesses: _filterBySearch(overviewBusinesses),
|
|
widescreen: widescreen,
|
|
selectable: true);
|
|
} else {
|
|
return SliverToBoxAdapter(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8.0),
|
|
alignment: Alignment.center,
|
|
child: const SizedBox(
|
|
width: 75,
|
|
height: 75,
|
|
child: RiveAnimation.asset(
|
|
'assets/mdev_triangle_loading.riv'),
|
|
),
|
|
));
|
|
}
|
|
}
|
|
return SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
'\nError: ${snapshot.error}',
|
|
style: const TextStyle(fontSize: 18),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
const SliverToBoxAdapter(
|
|
child: SizedBox(
|
|
height: 100,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SegmentedButton extends StatefulWidget {
|
|
final void Function(String) callback;
|
|
final String docType;
|
|
|
|
const _SegmentedButton({required this.callback, required this.docType});
|
|
|
|
@override
|
|
State<_SegmentedButton> createState() => _SegmentedButtonState();
|
|
}
|
|
|
|
class _SegmentedButtonState extends State<_SegmentedButton> {
|
|
Set<String> _selected = {};
|
|
|
|
void updateSelected(Set<String> newSelection) {
|
|
setState(() {
|
|
_selected = newSelection;
|
|
});
|
|
widget.callback(newSelection.first);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_selected = {widget.docType};
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: SegmentedButton(
|
|
segments: const <ButtonSegment<String>>[
|
|
ButtonSegment<String>(
|
|
value: 'Business',
|
|
label: Text('Businesses'),
|
|
icon: Icon(Icons.business)),
|
|
ButtonSegment<String>(
|
|
value: 'Job Listing',
|
|
label: Text('Job Listings'),
|
|
icon: Icon(Icons.work))
|
|
],
|
|
selected: _selected,
|
|
onSelectionChanged: updateSelected,
|
|
style: SegmentedButton.styleFrom(
|
|
side: BorderSide(color: Theme.of(context).colorScheme.secondary),
|
|
)),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FAB extends StatefulWidget {
|
|
final String documentType;
|
|
final Map<JobType, List<Business>> groupedBusinesses;
|
|
final Set<DataTypeJob> selectedDataTypesJob;
|
|
final Set<DataTypeBusiness> selectedDataTypesBusiness;
|
|
|
|
const _FAB(
|
|
{required this.groupedBusinesses,
|
|
required this.documentType,
|
|
required this.selectedDataTypesJob,
|
|
required this.selectedDataTypesBusiness});
|
|
|
|
@override
|
|
State<_FAB> createState() => _FABState();
|
|
}
|
|
|
|
class _FABState extends State<_FAB> {
|
|
List<Business> allBusinesses = [];
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
for (JobType type in widget.groupedBusinesses.keys) {
|
|
allBusinesses.addAll(widget.groupedBusinesses[type]!);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FloatingActionButton(
|
|
child: _isLoading
|
|
? const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 3.0,
|
|
),
|
|
)
|
|
: const Icon(Icons.save_alt),
|
|
onPressed: () async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
Set<Business> generateBusinesses = <Business>{};
|
|
if (selectedBusinesses.isEmpty) {
|
|
generateBusinesses = Set<Business>.from(allBusinesses);
|
|
} else {
|
|
generateBusinesses = selectedBusinesses;
|
|
}
|
|
|
|
await _generatePDF(context, widget.documentType, generateBusinesses,
|
|
widget.selectedDataTypesBusiness, widget.selectedDataTypesJob);
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
class _FilterDataTypeChips extends StatefulWidget {
|
|
final String documentType;
|
|
final Set<DataTypeJob> selectedDataTypesJob;
|
|
final Set<DataTypeBusiness> selectedDataTypesBusiness;
|
|
final void Function(
|
|
{Set<DataTypeJob>? selectedDataTypesJob,
|
|
Set<DataTypeBusiness>? selectedDataTypesBusiness}) updateCallback;
|
|
|
|
const _FilterDataTypeChips(this.documentType, this.selectedDataTypesJob,
|
|
this.selectedDataTypesBusiness, this.updateCallback);
|
|
|
|
@override
|
|
State<_FilterDataTypeChips> createState() => _FilterDataTypeChipsState();
|
|
}
|
|
|
|
class _FilterDataTypeChipsState extends State<_FilterDataTypeChips> {
|
|
List<Padding> filterDataTypeChips() {
|
|
List<Padding> chips = [];
|
|
|
|
if (widget.documentType == 'Business') {
|
|
for (var type in DataTypeBusiness.values) {
|
|
chips.add(Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 3.0, right: 3.0, bottom: 3.0, top: 3.0),
|
|
child: 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);
|
|
}
|
|
});
|
|
widget.updateCallback(
|
|
selectedDataTypesBusiness:
|
|
widget.selectedDataTypesBusiness);
|
|
}),
|
|
));
|
|
}
|
|
} else if (widget.documentType == 'Job Listing') {
|
|
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(
|
|
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);
|
|
}
|
|
});
|
|
widget.updateCallback(
|
|
selectedDataTypesJob: widget.selectedDataTypesJob);
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
return chips;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Wrap(
|
|
children: filterDataTypeChips(),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _generatePDF(
|
|
BuildContext context,
|
|
String documentType,
|
|
Set<Business>? selectedBusinesses,
|
|
Set<DataTypeBusiness>? dataTypesBusinessInput,
|
|
Set<DataTypeJob>? dataTypesJobInput) async {
|
|
Set<DataTypeBusiness> dataTypesBusiness = {};
|
|
Set<DataTypeJob> dataTypesJob = {};
|
|
List<pw.Widget> headerColumns = [];
|
|
List<pw.TableRow> tableRows = [];
|
|
List<Business> businesses = await fetchBusinesses(
|
|
selectedBusinesses!.map((business) => business.id).toList());
|
|
|
|
if (documentType == 'Business') {
|
|
dataTypesBusiness = Set.from(dataTypesBusinessInput!);
|
|
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(const pw.IconData(0xe0af), size: 24),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
}
|
|
for (DataTypeBusiness dataType in dataTypesBusiness) {
|
|
if (dataType != DataTypeBusiness.logo) {
|
|
businessRow.add(pw.Padding(
|
|
child: pw.Text(businessValueFromDataType(business, dataType)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
}
|
|
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 (documentType == 'Job Listing') {
|
|
dataTypesJob = Set.from(dataTypesJobInput!);
|
|
if (dataTypesJob.isEmpty) {
|
|
dataTypesJob.addAll(DataTypeJob.values);
|
|
}
|
|
List<DataTypeJob> dataTypesJobList =
|
|
sortDataTypesJob(dataTypesJob).toList();
|
|
|
|
List<Map<String, dynamic>> nameMapping = await fetchBusinessNames();
|
|
|
|
for (Business business in businesses) {
|
|
for (JobListing job in business.listings!) {
|
|
List<pw.Widget> jobRow = [];
|
|
for (DataTypeJob dataType in dataTypesJobList) {
|
|
jobRow.add(pw.Padding(
|
|
child: pw.Text(jobValueFromDataType(job, dataType, nameMapping)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
tableRows.add(pw.TableRow(children: jobRow));
|
|
}
|
|
}
|
|
|
|
for (var filter in dataTypesJobList) {
|
|
headerColumns.add(pw.Padding(
|
|
child: pw.Text(dataTypeFriendlyJob[filter]!,
|
|
style: const pw.TextStyle(fontSize: 10)),
|
|
padding: const pw.EdgeInsets.all(4.0)));
|
|
}
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
|
content: Text(
|
|
'Could not identify document type! Please select a type in the generation settings.')));
|
|
return;
|
|
}
|
|
|
|
// 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 =
|
|
'$documentType Data - ${dateTime.month}-${dateTime.day}-${dateTime.year} $time.pdf';
|
|
|
|
final pdf = pw.Document();
|
|
var svgBytes = await marinoDevLogo();
|
|
|
|
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: utf8.decode(svgBytes), height: 40),
|
|
pw.Padding(
|
|
padding: const pw.EdgeInsets.all(8.0),
|
|
child: pw.Text('$documentType 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: documentType == 'Business'
|
|
? _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) {
|
|
// List<int> fileInts = List.from(pdfBytes);
|
|
// html.AnchorElement(
|
|
// href:
|
|
// 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(fileInts)}')
|
|
// ..setAttribute('download', fileName)
|
|
// ..click();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
Map<int, pw.TableColumnWidth> _businessColumnSizes(
|
|
Set<DataTypeBusiness> dataTypes) {
|
|
double space = 744.0;
|
|
Map<int, pw.TableColumnWidth> map = {};
|
|
|
|
if (dataTypes.contains(DataTypeBusiness.logo)) {
|
|
space -= 28;
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.logo]!:
|
|
const pw.FixedColumnWidth(28)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactName)) {
|
|
space -= 72;
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.contactName]!:
|
|
const pw.FixedColumnWidth(72)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactPhone)) {
|
|
space -= 76;
|
|
map.addAll({
|
|
dataTypePriorityBusiness[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 += 2;
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.description)) {
|
|
leftNum += 3;
|
|
}
|
|
leftNum = space / leftNum;
|
|
if (dataTypes.contains(DataTypeBusiness.name)) {
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.name]!:
|
|
pw.FixedColumnWidth(leftNum)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.website)) {
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.website]!:
|
|
pw.FixedColumnWidth(leftNum)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.contactEmail)) {
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.contactEmail]!:
|
|
pw.FixedColumnWidth(leftNum)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.notes)) {
|
|
map.addAll({
|
|
dataTypePriorityBusiness[DataTypeBusiness.notes]!:
|
|
pw.FixedColumnWidth(leftNum * 2)
|
|
});
|
|
}
|
|
if (dataTypes.contains(DataTypeBusiness.description)) {
|
|
map.addAll({
|
|
dataTypePriorityBusiness[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.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.4)
|
|
});
|
|
}
|
|
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.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,
|
|
List<Map<String, dynamic>> nameMapping) {
|
|
switch (dataType) {
|
|
case DataTypeJob.name:
|
|
return job.name;
|
|
case DataTypeJob.description:
|
|
return job.description;
|
|
case DataTypeJob.wage:
|
|
return job.wage;
|
|
case DataTypeJob.link:
|
|
return job.link;
|
|
case DataTypeJob.businessName:
|
|
return nameMapping
|
|
.where((element) => element['id'] == job.businessId)
|
|
.first['name'];
|
|
}
|
|
}
|