feat: report page

This commit is contained in:
efrilm 2025-08-01 18:27:40 +07:00
parent 8e4a289625
commit e825e5daed
21 changed files with 851 additions and 776 deletions

View File

@ -1,4 +1,4 @@
# EnakloPOS # ApskelPOS
A new Flutter project. A new Flutter project.

View File

@ -11,7 +11,7 @@
<!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas --> <!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application <application
android:label="EnakloPOS" android:label="ApskelPOS"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon">
<activity <activity

View File

@ -4,31 +4,31 @@ import 'lib/core/utils/app_icon_generator.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
print('Generating EnakloPOS app icon...'); print('Generating ApskelPOS app icon...');
try { try {
final iconData = await AppIconGenerator.generateAppIcon(); final iconData = await AppIconGenerator.generateAppIcon();
// Ensure the assets/logo directory exists // Ensure the assets/logo directory exists
final logoDir = Directory('assets/logo'); final logoDir = Directory('assets/logo');
if (!await logoDir.exists()) { if (!await logoDir.exists()) {
await logoDir.create(recursive: true); await logoDir.create(recursive: true);
} }
// Write the generated icon to file // Write the generated icon to file
final iconFile = File('assets/logo/logo_app_icon.png'); final iconFile = File('assets/logo/logo_app_icon.png');
await iconFile.writeAsBytes(iconData); await iconFile.writeAsBytes(iconData);
print('✅ App icon generated successfully at: assets/logo/logo_app_icon.png'); print(
'✅ App icon generated successfully at: assets/logo/logo_app_icon.png');
print('📱 The icon features:'); print('📱 The icon features:');
print(' - White background for visibility'); print(' - White background for visibility');
print(' - Blue circular background'); print(' - Blue circular background');
print(' - Gift box with "e" inside'); print(' - Gift box with "e" inside');
print(' - "ENAKLO" and "POS" text'); print(' - "ENAKLO" and "POS" text');
print(' - 1024x1024 resolution for high quality'); print(' - 1024x1024 resolution for high quality');
} catch (e) { } catch (e) {
print('❌ Error generating app icon: $e'); print('❌ Error generating app icon: $e');
} }
} }

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>EnakloPOS</string> <string>ApskelPOS</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -38,7 +38,7 @@ class ItemSalesInvoice {
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Enaklo POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf); pdf: pdf);
} }
@ -48,7 +48,7 @@ class ItemSalesInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Enaklo POS | Item Sales Report', Text('Apskel POS | Item Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -22,13 +22,14 @@ class RevenueInvoice {
log("Starting PDF generation for summary report"); log("Starting PDF generation for summary report");
log("Summary model: ${summaryModel.toMap()}"); log("Summary model: ${summaryModel.toMap()}");
log("Search date formatted: $searchDateFormatted"); log("Search date formatted: $searchDateFormatted");
final pdf = Document(); final pdf = Document();
log("PDF document created"); log("PDF document created");
// Load logo image // Load logo image
log("Loading logo image..."); log("Loading logo image...");
final ByteData dataImage = await rootBundle.load('assets/images/logo.png'); final ByteData dataImage =
await rootBundle.load('assets/images/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List(); final Uint8List bytes = dataImage.buffer.asUint8List();
final image = pw.MemoryImage(bytes); final image = pw.MemoryImage(bytes);
log("Logo image loaded successfully, size: ${bytes.length} bytes"); log("Logo image loaded successfully, size: ${bytes.length} bytes");
@ -49,7 +50,7 @@ class RevenueInvoice {
log("Saving PDF document..."); log("Saving PDF document...");
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Enaklo POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf, pdf: pdf,
); );
} catch (e) { } catch (e) {
@ -69,7 +70,7 @@ class RevenueInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Enaklo POS | Summary Sales Report', Text('Apskel POS | Summary Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -93,7 +94,7 @@ class RevenueInvoice {
static Widget buildTotal(SummaryModel summaryModel) { static Widget buildTotal(SummaryModel summaryModel) {
log("Building total section with summary model: ${summaryModel.toMap()}"); log("Building total section with summary model: ${summaryModel.toMap()}");
// Helper function to safely parse string to int // Helper function to safely parse string to int
int safeParseInt(String? value) { int safeParseInt(String? value) {
if (value == null || value.isEmpty) return 0; if (value == null || value.isEmpty) return 0;
@ -104,7 +105,7 @@ class RevenueInvoice {
return 0; return 0;
} }
} }
return Container( return Container(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -125,7 +126,8 @@ class RevenueInvoice {
buildText( buildText(
title: 'Discount', title: 'Discount',
titleStyle: TextStyle(fontWeight: FontWeight.normal), titleStyle: TextStyle(fontWeight: FontWeight.normal),
value: "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}", value:
"- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
unite: true, unite: true,
textStyle: TextStyle( textStyle: TextStyle(
color: PdfColor.fromHex('#FF0000'), color: PdfColor.fromHex('#FF0000'),
@ -147,7 +149,8 @@ class RevenueInvoice {
titleStyle: TextStyle( titleStyle: TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
value: safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp, value:
safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
unite: true, unite: true,
), ),
Divider(), Divider(),

View File

@ -38,7 +38,7 @@ class TransactionSalesInvoice {
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Enaklo POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf); pdf: pdf);
} }
@ -48,7 +48,7 @@ class TransactionSalesInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Enaklo POS | Transaction Sales Report', Text('Apskel POS | Transaction Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -35,7 +35,7 @@ class PrintDataoutputs {
final total = totalPrice + pajak; final total = totalPrice + pajak;
bytes += generator.reset(); bytes += generator.reset();
bytes += generator.text('Enaklo POS', bytes += generator.text('Apskel POS',
styles: const PosStyles( styles: const PosStyles(
bold: true, bold: true,
align: PosAlign.center, align: PosAlign.center,
@ -202,7 +202,7 @@ class PrintDataoutputs {
// bytes += generator.feed(3); // bytes += generator.feed(3);
// } // }
bytes += generator.text('Enaklo POS', bytes += generator.text('Apskel POS',
styles: const PosStyles( styles: const PosStyles(
bold: true, bold: true,
align: PosAlign.center, align: PosAlign.center,
@ -474,21 +474,21 @@ class PrintDataoutputs {
} }
Future<List<int>> printOrderV3( Future<List<int>> printOrderV3(
List<ProductQuantity> products, List<ProductQuantity> products,
int totalQuantity, int totalQuantity,
int totalPrice, int totalPrice,
String paymentMethod, String paymentMethod,
int nominalBayar, int nominalBayar,
int kembalian, int kembalian,
int subTotal, int subTotal,
int discount, int discount,
int pajak, int pajak,
int serviceCharge, int serviceCharge,
String namaKasir, String namaKasir,
String customerName, String customerName,
int paper, int paper,
{int taxPercentage = 11, int serviceChargePercentage = 5} {int taxPercentage = 11,
) async { int serviceChargePercentage = 5}) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@ -778,8 +778,13 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printChecker(List<ProductQuantity> products, Future<List<int>> printChecker(
String tableName, String draftName, String cashierName, int paper, String orderType) async { List<ProductQuantity> products,
String tableName,
String draftName,
String cashierName,
int paper,
String orderType) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@ -908,8 +913,13 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printKitchen(List<ProductQuantity> products, Future<List<int>> printKitchen(
String tableNumber, String draftName, String cashierName, int paper, String orderType) async { List<ProductQuantity> products,
String tableNumber,
String draftName,
String cashierName,
int paper,
String orderType) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();

View File

@ -49,7 +49,7 @@ class _LoginPageState extends State<LoginPage> {
const SpaceHeight(24.0), const SpaceHeight(24.0),
const Center( const Center(
child: Text( child: Text(
'Enaklo POS', 'Apskel POS',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,

View File

@ -34,264 +34,314 @@ class _ReportPageState extends State<ReportPage> {
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30)); DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
DateTime toDate = DateTime.now(); DateTime toDate = DateTime.now();
@override
void initState() {
super.initState();
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate: DateFormatter.formatDateTime(fromDate),
endDate: DateFormatter.formatDateTime(toDate)),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String searchDateFormatted = String searchDateFormatted =
'${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}'; '${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}';
return Scaffold( return Scaffold(
body: Row( backgroundColor: AppColors.background,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// LEFT CONTENT ReportTitle(
Expanded( actionWidget: [
flex: 2, SizedBox(
child: Align( width: 300,
alignment: Alignment.topLeft, child: CustomDatePicker(
child: SingleChildScrollView( prefix: const Text('From: '),
padding: const EdgeInsets.all(24.0), initialDate: fromDate,
child: Column( onDateSelected: (selectedDate) {
crossAxisAlignment: CrossAxisAlignment.start, fromDate = selectedDate;
children: [
const ReportTitle(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: CustomDatePicker(
prefix: const Text('From: '),
initialDate: fromDate,
onDateSelected: (selectedDate) {
fromDate = selectedDate;
setState(() {}); setState(() {});
}, },
),
),
const SpaceWidth(24.0),
Flexible(
child: CustomDatePicker(
prefix: const Text('To: '),
initialDate: toDate,
onDateSelected: (selectedDate) {
toDate = selectedDate;
setState(() {});
// context.read<TransactionReportBloc>().add(
// TransactionReportEvent.getReport(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
// context.read<ItemSalesReportBloc>().add(
// ItemSalesReportEvent.getItemSales(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
},
),
),
],
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
children: [
ReportMenu(
label: 'Transaction Report',
onPressed: () {
selectedMenu = 0;
title = 'Transaction Report';
setState(() {});
//enddate is 1 month before the current date
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate: DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 0,
),
ReportMenu(
label: 'Item Sales Report',
onPressed: () {
selectedMenu = 1;
title = 'Item Sales Report';
setState(() {});
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
startDate: DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 1,
),
ReportMenu(
label: 'Product Sales Chart',
onPressed: () {
selectedMenu = 2;
title = 'Product Sales Chart';
setState(() {});
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime(fromDate),
DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 2,
),
ReportMenu(
label: 'Summary Sales Report',
onPressed: () {
selectedMenu = 3;
title = 'Summary Sales Report';
setState(() {});
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(
DateFormatter.formatDateTime(fromDate),
DateFormatter.formatDateTime(toDate)),
);
log("Date ${DateFormatter.formatDateTime(fromDate)}");
},
isActive: selectedMenu == 3,
),
ReportMenu(
label: 'Payment Method Report',
onPressed: () {
selectedMenu = 4;
title = 'Payment Method Report';
setState(() {});
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent.getPaymentMethodReport(
startDate: DateFormatter.formatDateTime(fromDate),
endDate: DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 4,
),
],
),
),
],
), ),
), ),
const SpaceWidth(24.0),
SizedBox(
width: 300,
child: CustomDatePicker(
prefix: const Text('To: '),
initialDate: toDate,
onDateSelected: (selectedDate) {
toDate = selectedDate;
setState(() {});
// context.read<TransactionReportBloc>().add(
// TransactionReportEvent.getReport(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
// context.read<ItemSalesReportBloc>().add(
// ItemSalesReportEvent.getItemSales(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
},
),
),
],
),
Expanded(
child: Row(
children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Material(
color: AppColors.white,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReportMenu(
label: 'Laporan Transaksi',
subtitle:
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
icon: Icons.receipt_long_outlined,
onPressed: () {
selectedMenu = 0;
title = 'Laporan Transaksi';
setState(() {});
//enddate is 1 month before the current date
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 0,
),
ReportMenu(
label: 'Laporan Penjualan Item',
subtitle:
'Laporan penjualan berdasarkan masing-masing item atau produk.',
icon: Icons.inventory_2_outlined,
onPressed: () {
selectedMenu = 1;
title = 'Laporan Penjualan Item';
setState(() {});
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 1,
),
ReportMenu(
label: 'Chart Penjualan Produk',
subtitle:
'Grafik visual penjualan produk untuk analisa performa penjualan.',
icon: Icons.bar_chart_outlined,
onPressed: () {
selectedMenu = 2;
title = 'Chart Penjualan Produk';
setState(() {});
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 2,
),
ReportMenu(
label: 'Ringkasan Laporan Penjualan',
subtitle:
'Ringkasan total penjualan dalam periode tertentu.',
icon: Icons.insert_drive_file_outlined,
onPressed: () {
selectedMenu = 3;
title = 'Ringkasan Laporan Penjualan';
setState(() {});
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(
DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)),
);
log("Date ${DateFormatter.formatDateTime(fromDate)}");
},
isActive: selectedMenu == 3,
),
ReportMenu(
label: 'Laporan Metode Pembayaran',
subtitle:
'Laporan metode pembayaran yang digunakan.',
icon: Icons.payment_outlined,
onPressed: () {
selectedMenu = 4;
title = 'Laporan Metode Pembayaran';
setState(() {});
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent
.getPaymentMethodReport(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate:
DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 4,
),
],
),
),
),
),
),
// RIGHT CONTENT
Expanded(
flex: 4,
child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc,
TransactionReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (transactionReport) {
return TransactionReportWidget(
transactionReport: transactionReport,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getTitleReportPageWidget(),
);
},
);
},
)
: selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc,
ItemSalesReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (itemSales) {
return ItemSalesReportWidget(
itemSales: itemSales,
title: title,
searchDateFormatted:
searchDateFormatted,
headerWidgets:
_getItemSalesPageWidget(),
);
},
);
},
)
: selectedMenu == 2
? BlocBuilder<ProductSalesBloc,
ProductSalesState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (productSales) {
return ProductSalesChartWidgets(
title: title,
searchDateFormatted:
searchDateFormatted,
productSales: productSales,
);
},
);
},
)
: selectedMenu == 3
? BlocBuilder<SummaryBloc, SummaryState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (summary) {
return SummaryReportWidget(
summary: summary,
title: title,
searchDateFormatted:
searchDateFormatted,
);
},
);
},
)
: selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc,
PaymentMethodReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (paymentMethodData) {
return PaymentMethodReportWidget(
paymentMethodData:
paymentMethodData,
title: title,
searchDateFormatted:
searchDateFormatted,
headerWidgets:
_getPaymentMethodPageWidget(),
);
},
);
},
)
: const SizedBox.shrink()),
],
), ),
), ),
// RIGHT CONTENT
Expanded(
flex: 2,
child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc, TransactionReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (transactionReport) {
return TransactionReportWidget(
transactionReport: transactionReport,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getTitleReportPageWidget(),
);
},
);
},
)
: selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc, ItemSalesReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (itemSales) {
return ItemSalesReportWidget(
itemSales: itemSales,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getItemSalesPageWidget(),
);
},
);
},
)
: selectedMenu == 2
? BlocBuilder<ProductSalesBloc, ProductSalesState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (productSales) {
return ProductSalesChartWidgets(
title: title,
searchDateFormatted: searchDateFormatted,
productSales: productSales,
);
},
);
},
)
: selectedMenu == 3
? BlocBuilder<SummaryBloc, SummaryState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (summary) {
return SummaryReportWidget(
summary: summary,
title: title,
searchDateFormatted: searchDateFormatted,
);
},
);
},
)
: selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc, PaymentMethodReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (paymentMethodData) {
return PaymentMethodReportWidget(
paymentMethodData: paymentMethodData,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getPaymentMethodPageWidget(),
);
},
);
},
)
: const SizedBox.shrink()),
], ],
), ),
); );
@ -314,11 +364,11 @@ class _ReportPageState extends State<ReportPage> {
List<Widget> _getItemSalesPageWidget() { List<Widget> _getItemSalesPageWidget() {
return [ return [
_getTitleItemWidget('ID', 80), _getTitleItemWidget('ID', 80),
_getTitleItemWidget('Order', 60), _getTitleItemWidget('Order', 100),
_getTitleItemWidget('Product', 160), _getTitleItemWidget('Product', 200),
_getTitleItemWidget('Qty', 60), _getTitleItemWidget('Qty', 60),
_getTitleItemWidget('Price', 140), _getTitleItemWidget('Price', 150),
_getTitleItemWidget('Total Price', 140), _getTitleItemWidget('Total Price', 160),
]; ];
} }

View File

@ -4,12 +4,12 @@ import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/utils/item_sales_invoice.dart'; import 'package:enaklo_pos/core/utils/item_sales_invoice.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart'; import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart'; import 'package:horizontal_data_table/horizontal_data_table.dart';
import 'package:permission_handler/permission_handler.dart';
class ItemSalesReportWidget extends StatelessWidget { class ItemSalesReportWidget extends StatelessWidget {
final String title; final String title;
@ -26,166 +26,123 @@ class ItemSalesReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Column( ReportPageTitle(
children: [ title: title,
const SpaceHeight(24.0), searchDateFormatted: searchDateFormatted,
Center( onExport: () async {
child: Text( try {
title, final status = await PermessionHelper().checkPermission();
style: if (status) {
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), final pdfFile = await ItemSalesInvoice.generate(
), itemSales, searchDateFormatted);
), log("pdfFile: $pdfFile");
const SizedBox( await HelperPdfService.openFile(pdfFile);
height: 8.0, } else {
), ScaffoldMessenger.of(context).showSnackBar(
Padding( const SnackBar(
padding: const EdgeInsets.symmetric(horizontal: 12), content: Text('Storage permission is required to save PDF'),
child: Row( backgroundColor: Colors.red,
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ );
Text( }
searchDateFormatted, } catch (e) {
style: const TextStyle(fontSize: 16.0), log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
), ),
GestureDetector( );
onTap: () async { }
try { },
final status = await PermessionHelper().checkPermission(); ),
if (status) { const SpaceHeight(16.0),
final pdfFile = await ItemSalesInvoice.generate( Expanded(
itemSales, searchDateFormatted); child: Padding(
log("pdfFile: $pdfFile"); padding: const EdgeInsets.all(12),
await HelperPdfService.openFile(pdfFile); child: ClipRRect(
} else { borderRadius: BorderRadius.circular(12),
ScaffoldMessenger.of(context).showSnackBar( child: HorizontalDataTable(
const SnackBar( leftHandSideColumnWidth: 80,
content: Text('Storage permission is required to save PDF'), rightHandSideColumnWidth: 670,
backgroundColor: Colors.red, isFixedHeader: true,
), headerWidgets: headerWidgets,
);
} // isFixedFooter: true,
} catch (e) { // footerWidgets: _getTitleWidget(),
log("Error generating PDF: $e"); leftSideItemBuilder: (context, index) {
ScaffoldMessenger.of(context).showSnackBar( return Container(
SnackBar( width: 80,
content: Text('Failed to generate PDF: $e'), height: 52,
backgroundColor: Colors.red, alignment: Alignment.centerLeft,
), child: Center(child: Text(itemSales[index].id.toString())),
); );
} },
}, rightSideItemBuilder: (context, index) {
child: const Row( return Row(
children: [ children: <Widget>[
Text( Container(
"PDF", width: 100,
style: TextStyle( height: 52,
fontSize: 14.0, alignment: Alignment.centerLeft,
fontWeight: FontWeight.bold, child: Center(
color: AppColors.primary, child: Text(itemSales[index].orderId.toString())),
), ),
Container(
width: 200,
height: 52,
alignment: Alignment.centerLeft,
child:
Center(child: Text(itemSales[index].productName!)),
),
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].quantity.toString())),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
itemSales[index].price!.currencyFormatRp,
)),
),
Container(
width: 160,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
(itemSales[index].price! * itemSales[index].quantity!)
.currencyFormatRp,
)),
), ),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
], ],
), );
},
itemCount: itemSales.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
), ),
], leftHandSideColBackgroundColor: AppColors.white,
), rightHandSideColBackgroundColor: AppColors.white,
),
const SpaceHeight(16.0),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 80,
rightHandSideColumnWidth: 560,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true, itemExtent: 55,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 80,
height: 52,
alignment: Alignment.centerLeft,
child:
Center(child: Text(itemSales[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].orderId.toString())),
),
Container(
width: 160,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].productName!)),
),
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child:
Text(itemSales[index].quantity.toString())),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
itemSales[index].price!.currencyFormatRp,
)),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
(itemSales[index].price! *
itemSales[index].quantity!)
.currencyFormatRp,
)),
),
],
);
},
itemCount: itemSales.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55,
),
), ),
), ),
), ),
], ),
), ],
); );
} }
} }

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
import '../../../core/components/spaces.dart'; import '../../../core/components/spaces.dart';
@ -109,20 +107,20 @@ class PaymentMethodReportWidget extends StatelessWidget {
), ),
), ),
child: Row( child: Row(
children: [ children: [
_getBodyItemWidget( _getBodyItemWidget(
item.paymentMethod ?? '-', item.paymentMethod ?? '-',
180, 180,
), ),
_getBodyItemWidget( _getBodyItemWidget(
item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0', item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0',
180, 180,
), ),
_getBodyItemWidget( _getBodyItemWidget(
item.transactionCount?.toString() ?? '0', item.transactionCount?.toString() ?? '0',
180, 180,
), ),
], ],
), ),
); );
}).toList() ?? }).toList() ??
@ -150,4 +148,4 @@ class PaymentMethodReportWidget extends StatelessWidget {
), ),
); );
} }
} }

View File

@ -1,6 +1,7 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart'; import 'package:pie_chart/pie_chart.dart';
@ -67,59 +68,61 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Padding( ReportPageTitle(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), title: widget.title,
child: Column( searchDateFormatted: widget.searchDateFormatted,
children: [ onExport: () async {},
const SpaceHeight(24.0), isExport: false, // Set to false if export is not needed
Center(
child: Text(
widget.title,
style: const TextStyle(
fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
Center(
child: Text(
widget.searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
),
const SpaceHeight(16.0),
PieChart(
dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800),
chartLegendSpacing: 32,
chartRadius: MediaQuery.of(context).size.width / 3.2,
colorList: colorList,
initialAngleInDegree: 0,
chartType: ChartType.disc,
ringStrokeWidth: 32,
// centerText: "HYBRID",
legendOptions: LegendOptions(
showLegendsInRow: false,
legendPosition: LegendPosition.right,
showLegends: true,
legendShape: BoxShape.circle,
legendTextStyle: TextStyle(
fontWeight: FontWeight.bold,
),
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
showChartValues: true,
showChartValuesInPercentage: false,
showChartValuesOutside: false,
decimalPlaces: 0,
),
// gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient---
)
],
), ),
), const SpaceHeight(16.0),
Expanded(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
PieChart(
dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800),
chartLegendSpacing: 32,
chartRadius: MediaQuery.of(context).size.width / 3.2,
colorList: colorList,
initialAngleInDegree: 0,
chartType: ChartType.disc,
ringStrokeWidth: 32,
// centerText: "HYBRID",
legendOptions: LegendOptions(
showLegendsInRow: false,
legendPosition: LegendPosition.right,
showLegends: true,
legendShape: BoxShape.circle,
legendTextStyle: TextStyle(
fontWeight: FontWeight.bold,
),
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
showChartValues: true,
showChartValuesInPercentage: false,
showChartValuesOutside: false,
decimalPlaces: 0,
),
// gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient---
),
],
),
),
),
)
],
); );
} }
} }

View File

@ -1,13 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart'; import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class ReportMenu extends StatelessWidget { class ReportMenu extends StatelessWidget {
final String label; final String label;
final String subtitle;
final IconData icon;
final VoidCallback onPressed; final VoidCallback onPressed;
final bool isActive; final bool isActive;
@ -16,6 +15,8 @@ class ReportMenu extends StatelessWidget {
required this.label, required this.label,
required this.onPressed, required this.onPressed,
required this.isActive, required this.isActive,
required this.subtitle,
required this.icon,
}); });
@override @override
@ -23,38 +24,43 @@ class ReportMenu extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onPressed, onTap: onPressed,
child: Container( child: Container(
margin: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(12.0),
width: 180.0,
height: 160.0,
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: border: Border(
isActive ? AppColors.primary.withOpacity(0.13) : AppColors.white, right: BorderSide(
borderRadius: BorderRadius.circular(18.0), color: isActive ? AppColors.primary : Colors.transparent,
border: Border.all( width: 4.0,
color: isActive ? AppColors.primary : AppColors.stroke, ),
), ),
), ),
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Assets.icons.report.svg( Icon(
colorFilter: isActive icon,
? const ColorFilter.mode( size: 24.0,
AppColors.primary, color: isActive ? AppColors.primary : AppColors.grey,
BlendMode.srcIn,
)
: null,
), ),
const SpaceHeight(28.0), const SpaceWidth(12),
Text( Expanded(
label, child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
color: isActive ? AppColors.primary : AppColors.black, children: [
fontWeight: FontWeight.w600, Text(
label,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4.0),
Text(
subtitle,
style:
const TextStyle(fontSize: 14.0, color: AppColors.grey),
),
],
), ),
), ),
const SpaceHeight(24.0),
], ],
), ),
), ),

View File

@ -0,0 +1,65 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:flutter/material.dart';
class ReportPageTitle extends StatelessWidget {
final String title;
final String searchDateFormatted;
final Function() onExport;
final bool isExport;
const ReportPageTitle(
{super.key,
required this.title,
required this.searchDateFormatted,
required this.onExport,
this.isExport = true});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(color: AppColors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w800, fontSize: 16.0),
),
const SizedBox(
height: 8.0,
),
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
],
),
if (isExport)
GestureDetector(
onTap: onExport,
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
);
}
}

View File

@ -1,38 +1,60 @@
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class ReportTitle extends StatelessWidget { class ReportTitle extends StatelessWidget {
const ReportTitle({super.key}); final List<Widget>? actionWidget;
const ReportTitle({super.key, this.actionWidget});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Container(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.symmetric(
children: [ horizontal: 16.0,
const Text( vertical: 10.0,
'Report', ),
style: TextStyle( width: double.infinity,
color: AppColors.primary, height: context.deviceHeight * 0.1,
fontSize: 28, decoration: const BoxDecoration(
fontWeight: FontWeight.w600, color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
), ),
), ),
const SpaceHeight(4.0), ),
Text( child: Row(
DateTime.now().toFormattedDate(), mainAxisAlignment: MainAxisAlignment.spaceBetween,
style: const TextStyle( children: [
color: AppColors.subtitle, Column(
fontSize: 16, crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Laporan',
style: TextStyle(
color: AppColors.black,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
Text(
DateTime.now().toFormattedDate2(),
style: TextStyle(
color: AppColors.grey,
fontSize: 14,
),
),
],
), ),
), if (actionWidget != null)
const SpaceHeight(20.0), Row(
const Divider(), children: actionWidget!,
], ),
],
),
); );
} }
} }

View File

@ -9,7 +9,6 @@ import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/data/models/response/summary_response_model.dart'; import 'package:enaklo_pos/data/models/response/summary_response_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/revenue_invoice.dart'; import '../../../core/utils/revenue_invoice.dart';
@ -55,27 +54,29 @@ class SummaryReportWidget extends StatelessWidget {
log("PDF button clicked for summary report"); log("PDF button clicked for summary report");
try { try {
log("Checking permissions..."); log("Checking permissions...");
final status = await PermessionHelper().checkPermission(); final status =
await PermessionHelper().checkPermission();
log("Permission status: $status"); log("Permission status: $status");
if (status) { if (status) {
log("Permission granted, starting PDF generation..."); log("Permission granted, starting PDF generation...");
log("Summary data: ${summary.toMap()}"); log("Summary data: ${summary.toMap()}");
log("Search date: $searchDateFormatted"); log("Search date: $searchDateFormatted");
final pdfFile = await RevenueInvoice.generate( final pdfFile = await RevenueInvoice.generate(
summary, searchDateFormatted); summary, searchDateFormatted);
log("PDF file generated: ${pdfFile.path}"); log("PDF file generated: ${pdfFile.path}");
log("PDF file exists: ${await pdfFile.exists()}"); log("PDF file exists: ${await pdfFile.exists()}");
log("PDF file size: ${await pdfFile.length()} bytes"); log("PDF file size: ${await pdfFile.length()} bytes");
log("Attempting to open PDF file..."); log("Attempting to open PDF file...");
await HelperPdfService.openFile(pdfFile); await HelperPdfService.openFile(pdfFile);
log("PDF file opened successfully"); log("PDF file opened successfully");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('PDF saved successfully: ${pdfFile.path}'), content: Text(
'PDF saved successfully: ${pdfFile.path}'),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
@ -83,7 +84,8 @@ class SummaryReportWidget extends StatelessWidget {
log("Permission denied"); log("Permission denied");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Storage permission is required to save PDF'), content: Text(
'Storage permission is required to save PDF'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
), ),
); );

View File

@ -5,12 +5,12 @@ import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart'; import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart';
import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart'; import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart'; import 'package:horizontal_data_table/horizontal_data_table.dart';
import 'package:permission_handler/permission_handler.dart';
class TransactionReportWidget extends StatelessWidget { class TransactionReportWidget extends StatelessWidget {
final String title; final String title;
@ -27,208 +27,168 @@ class TransactionReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Column( ReportPageTitle(
children: [ title: title,
const SpaceHeight(24.0), searchDateFormatted: searchDateFormatted,
Center( onExport: () async {
child: Text( try {
title, final status = await PermessionHelper().checkPermission();
style: if (status) {
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), final pdfFile = await TransactionSalesInvoice.generate(
), transactionReport, searchDateFormatted);
), log("pdfFile: $pdfFile");
const SizedBox( await HelperPdfService.openFile(pdfFile);
height: 8.0, } else {
), ScaffoldMessenger.of(context).showSnackBar(
Padding( const SnackBar(
padding: const EdgeInsets.symmetric(horizontal: 12), content: Text('Storage permission is required to save PDF'),
child: Row( backgroundColor: Colors.red,
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ );
Text( }
searchDateFormatted, } catch (e) {
style: const TextStyle(fontSize: 16.0), log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
), ),
GestureDetector( );
onTap: () async { }
try { },
final status = await PermessionHelper().checkPermission(); ),
if (status) { const SpaceHeight(16.0),
final pdfFile = await TransactionSalesInvoice.generate( Expanded(
transactionReport, searchDateFormatted); child: Padding(
log("pdfFile: $pdfFile"); padding: const EdgeInsets.all(12),
await HelperPdfService.openFile(pdfFile); child: ClipRRect(
} else { borderRadius: BorderRadius.circular(12),
ScaffoldMessenger.of(context).showSnackBar( child: HorizontalDataTable(
const SnackBar( leftHandSideColumnWidth: 50,
content: Text('Storage permission is required to save PDF'), rightHandSideColumnWidth: 1020,
backgroundColor: Colors.red, isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 40,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].total!.currencyFormatRp,
)),
),
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].subTotal!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].tax!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
int.parse(transactionReport[index]
.discountAmount!
.replaceAll('.00', ''))
.currencyFormatRp,
), ),
);
}
} catch (e) {
log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
), ),
), ),
Icon( Container(
Icons.download_outlined, width: 100,
color: AppColors.primary, height: 52,
) padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index]
.serviceCharge!
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].totalItem.toString()),
),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].namaKasir!),
),
),
Container(
width: 230,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index]
.transactionTime!
.toFormattedDate()),
),
),
], ],
), );
},
itemCount: transactionReport.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
), ),
], leftHandSideColBackgroundColor: AppColors.white,
), rightHandSideColBackgroundColor: AppColors.white,
),
const SpaceHeight(16.0),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 50,
rightHandSideColumnWidth: 1020,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 40,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].total!.currencyFormatRp,
)),
),
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].subTotal!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].tax!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
int.parse(transactionReport[index]
.discountAmount!
.replaceAll('.00', ''))
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index]
.serviceCharge!
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].totalItem.toString()),
),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].namaKasir!),
),
),
Container(
width: 230,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index]
.transactionTime!
.toFormattedDate()),
),
),
],
);
},
itemCount: transactionReport.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55, itemExtent: 55,
),
), ),
), ),
), ),
], ),
), ],
); );
} }
} }

View File

@ -32,7 +32,7 @@ class _SalesPageState extends State<SalesPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
'Enaklo POS ', 'Apskel POS ',
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.primary,
fontSize: 22, fontSize: 22,

View File

@ -171,8 +171,7 @@ class _ManagePrinterPageState extends State<ManagePrinterPage> {
//bytes += generator.setGlobalFont(PosFontType.fontA); //bytes += generator.setGlobalFont(PosFontType.fontA);
bytes += generator.reset(); bytes += generator.reset();
bytes += bytes += generator.text('Apskel POS', styles: const PosStyles(bold: true));
generator.text('Enaklo POS', styles: const PosStyles(bold: true));
bytes += bytes +=
generator.text('Reverse text', styles: const PosStyles(reverse: true)); generator.text('Reverse text', styles: const PosStyles(reverse: true));
bytes += generator.text('Underlined text', bytes += generator.text('Underlined text',

View File

@ -1,5 +1,5 @@
name: enaklo_pos name: enaklo_pos
description: "EnakloPOS - Point of Sale Application" description: "ApskelPOS - Point of Sale Application"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev