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

@ -5,7 +5,7 @@ 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();
@ -20,14 +20,14 @@ void main() async {
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

@ -28,7 +28,8 @@ class RevenueInvoice {
// 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,
@ -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,
@ -487,8 +487,8 @@ class PrintDataoutputs {
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,28 +34,29 @@ 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,
children: [ body: Column(
// LEFT CONTENT
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const ReportTitle(), ReportTitle(
Row( actionWidget: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, SizedBox(
children: [ width: 300,
Flexible(
child: CustomDatePicker( child: CustomDatePicker(
prefix: const Text('From: '), prefix: const Text('From: '),
initialDate: fromDate, initialDate: fromDate,
@ -67,7 +68,8 @@ class _ReportPageState extends State<ReportPage> {
), ),
), ),
const SpaceWidth(24.0), const SpaceWidth(24.0),
Flexible( SizedBox(
width: 300,
child: CustomDatePicker( child: CustomDatePicker(
prefix: const Text('To: '), prefix: const Text('To: '),
initialDate: toDate, initialDate: toDate,
@ -95,20 +97,34 @@ class _ReportPageState extends State<ReportPage> {
), ),
], ],
), ),
Padding( Expanded(
padding: const EdgeInsets.all(15.0), child: Row(
child: Wrap( children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Material(
color: AppColors.white,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ReportMenu( ReportMenu(
label: 'Transaction Report', label: 'Laporan Transaksi',
subtitle:
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
icon: Icons.receipt_long_outlined,
onPressed: () { onPressed: () {
selectedMenu = 0; selectedMenu = 0;
title = 'Transaction Report'; title = 'Laporan Transaksi';
setState(() {}); setState(() {});
//enddate is 1 month before the current date //enddate is 1 month before the current date
context.read<TransactionReportBloc>().add( context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport( TransactionReportEvent.getReport(
startDate: DateFormatter.formatDateTime( startDate:
DateFormatter.formatDateTime(
fromDate), fromDate),
endDate: DateFormatter.formatDateTime( endDate: DateFormatter.formatDateTime(
toDate)), toDate)),
@ -117,14 +133,18 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 0, isActive: selectedMenu == 0,
), ),
ReportMenu( ReportMenu(
label: 'Item Sales Report', label: 'Laporan Penjualan Item',
subtitle:
'Laporan penjualan berdasarkan masing-masing item atau produk.',
icon: Icons.inventory_2_outlined,
onPressed: () { onPressed: () {
selectedMenu = 1; selectedMenu = 1;
title = 'Item Sales Report'; title = 'Laporan Penjualan Item';
setState(() {}); setState(() {});
context.read<ItemSalesReportBloc>().add( context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales( ItemSalesReportEvent.getItemSales(
startDate: DateFormatter.formatDateTime( startDate:
DateFormatter.formatDateTime(
fromDate), fromDate),
endDate: DateFormatter.formatDateTime( endDate: DateFormatter.formatDateTime(
toDate)), toDate)),
@ -133,28 +153,36 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 1, isActive: selectedMenu == 1,
), ),
ReportMenu( ReportMenu(
label: 'Product Sales Chart', label: 'Chart Penjualan Produk',
subtitle:
'Grafik visual penjualan produk untuk analisa performa penjualan.',
icon: Icons.bar_chart_outlined,
onPressed: () { onPressed: () {
selectedMenu = 2; selectedMenu = 2;
title = 'Product Sales Chart'; title = 'Chart Penjualan Produk';
setState(() {}); setState(() {});
context.read<ProductSalesBloc>().add( context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales( ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime(fromDate), DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)), DateFormatter.formatDateTime(toDate)),
); );
}, },
isActive: selectedMenu == 2, isActive: selectedMenu == 2,
), ),
ReportMenu( ReportMenu(
label: 'Summary Sales Report', label: 'Ringkasan Laporan Penjualan',
subtitle:
'Ringkasan total penjualan dalam periode tertentu.',
icon: Icons.insert_drive_file_outlined,
onPressed: () { onPressed: () {
selectedMenu = 3; selectedMenu = 3;
title = 'Summary Sales Report'; title = 'Ringkasan Laporan Penjualan';
setState(() {}); setState(() {});
context.read<SummaryBloc>().add( context.read<SummaryBloc>().add(
SummaryEvent.getSummary( SummaryEvent.getSummary(
DateFormatter.formatDateTime(fromDate), DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)), DateFormatter.formatDateTime(toDate)),
); );
@ -163,15 +191,23 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 3, isActive: selectedMenu == 3,
), ),
ReportMenu( ReportMenu(
label: 'Payment Method Report', label: 'Laporan Metode Pembayaran',
subtitle:
'Laporan metode pembayaran yang digunakan.',
icon: Icons.payment_outlined,
onPressed: () { onPressed: () {
selectedMenu = 4; selectedMenu = 4;
title = 'Payment Method Report'; title = 'Laporan Metode Pembayaran';
setState(() {}); setState(() {});
context.read<PaymentMethodReportBloc>().add( context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent.getPaymentMethodReport( PaymentMethodReportEvent
startDate: DateFormatter.formatDateTime(fromDate), .getPaymentMethodReport(
endDate: DateFormatter.formatDateTime(toDate)), startDate:
DateFormatter.formatDateTime(
fromDate),
endDate:
DateFormatter.formatDateTime(
toDate)),
); );
}, },
isActive: selectedMenu == 4, isActive: selectedMenu == 4,
@ -179,17 +215,16 @@ class _ReportPageState extends State<ReportPage> {
], ],
), ),
), ),
],
),
), ),
), ),
), ),
// RIGHT CONTENT // RIGHT CONTENT
Expanded( Expanded(
flex: 2, flex: 4,
child: selectedMenu == 0 child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc, TransactionReportState>( ? BlocBuilder<TransactionReportBloc,
TransactionReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -210,7 +245,8 @@ class _ReportPageState extends State<ReportPage> {
}, },
) )
: selectedMenu == 1 : selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc, ItemSalesReportState>( ? BlocBuilder<ItemSalesReportBloc,
ItemSalesReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -223,15 +259,18 @@ class _ReportPageState extends State<ReportPage> {
return ItemSalesReportWidget( return ItemSalesReportWidget(
itemSales: itemSales, itemSales: itemSales,
title: title, title: title,
searchDateFormatted: searchDateFormatted, searchDateFormatted:
headerWidgets: _getItemSalesPageWidget(), searchDateFormatted,
headerWidgets:
_getItemSalesPageWidget(),
); );
}, },
); );
}, },
) )
: selectedMenu == 2 : selectedMenu == 2
? BlocBuilder<ProductSalesBloc, ProductSalesState>( ? BlocBuilder<ProductSalesBloc,
ProductSalesState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -243,7 +282,8 @@ class _ReportPageState extends State<ReportPage> {
success: (productSales) { success: (productSales) {
return ProductSalesChartWidgets( return ProductSalesChartWidgets(
title: title, title: title,
searchDateFormatted: searchDateFormatted, searchDateFormatted:
searchDateFormatted,
productSales: productSales, productSales: productSales,
); );
}, },
@ -255,7 +295,8 @@ class _ReportPageState extends State<ReportPage> {
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
child: CircularProgressIndicator(), child:
CircularProgressIndicator(),
), ),
error: (message) { error: (message) {
return Text(message); return Text(message);
@ -264,28 +305,34 @@ class _ReportPageState extends State<ReportPage> {
return SummaryReportWidget( return SummaryReportWidget(
summary: summary, summary: summary,
title: title, title: title,
searchDateFormatted: searchDateFormatted, searchDateFormatted:
searchDateFormatted,
); );
}, },
); );
}, },
) )
: selectedMenu == 4 : selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc, PaymentMethodReportState>( ? BlocBuilder<PaymentMethodReportBloc,
PaymentMethodReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
child: CircularProgressIndicator(), child:
CircularProgressIndicator(),
), ),
error: (message) { error: (message) {
return Text(message); return Text(message);
}, },
loaded: (paymentMethodData) { loaded: (paymentMethodData) {
return PaymentMethodReportWidget( return PaymentMethodReportWidget(
paymentMethodData: paymentMethodData, paymentMethodData:
paymentMethodData,
title: title, title: title,
searchDateFormatted: searchDateFormatted, searchDateFormatted:
headerWidgets: _getPaymentMethodPageWidget(), searchDateFormatted,
headerWidgets:
_getPaymentMethodPageWidget(),
); );
}, },
); );
@ -294,6 +341,9 @@ class _ReportPageState extends State<ReportPage> {
: const SizedBox.shrink()), : 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,32 +26,12 @@ 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),
child: Column(
children: [ children: [
const SpaceHeight(24.0), ReportPageTitle(
Center( title: title,
child: Text( searchDateFormatted: searchDateFormatted,
title, onExport: () async {
style:
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
const SizedBox(
height: 8.0,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
GestureDetector(
onTap: () async {
try { try {
final status = await PermessionHelper().checkPermission(); final status = await PermessionHelper().checkPermission();
if (status) { if (status) {
@ -77,25 +57,6 @@ class ItemSalesReportWidget extends StatelessWidget {
); );
} }
}, },
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Expanded( Expanded(
@ -105,7 +66,7 @@ class ItemSalesReportWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable( child: HorizontalDataTable(
leftHandSideColumnWidth: 80, leftHandSideColumnWidth: 80,
rightHandSideColumnWidth: 560, rightHandSideColumnWidth: 670,
isFixedHeader: true, isFixedHeader: true,
headerWidgets: headerWidgets, headerWidgets: headerWidgets,
@ -116,37 +77,35 @@ class ItemSalesReportWidget extends StatelessWidget {
width: 80, width: 80,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: child: Center(child: Text(itemSales[index].id.toString())),
Center(child: Text(itemSales[index].id.toString())),
); );
}, },
rightSideItemBuilder: (context, index) { rightSideItemBuilder: (context, index) {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Container( Container(
width: 60, width: 100,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Text(itemSales[index].orderId.toString())), child: Text(itemSales[index].orderId.toString())),
), ),
Container( Container(
width: 160, width: 200,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child:
child: Text(itemSales[index].productName!)), Center(child: Text(itemSales[index].productName!)),
), ),
Container( Container(
width: 60, width: 60,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: child: Text(itemSales[index].quantity.toString())),
Text(itemSales[index].quantity.toString())),
), ),
Container( Container(
width: 140, width: 150,
height: 52, height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@ -156,14 +115,13 @@ class ItemSalesReportWidget extends StatelessWidget {
)), )),
), ),
Container( Container(
width: 140, width: 160,
height: 52, height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Text( child: Text(
(itemSales[index].price! * (itemSales[index].price! * itemSales[index].quantity!)
itemSales[index].quantity!)
.currencyFormatRp, .currencyFormatRp,
)), )),
), ),
@ -185,7 +143,6 @@ class ItemSalesReportWidget extends StatelessWidget {
), ),
), ),
], ],
),
); );
} }
} }

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';

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,27 +68,26 @@ 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),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
child: Column(
children: [ children: [
const SpaceHeight(24.0), ReportPageTitle(
Center( title: widget.title,
child: Text( searchDateFormatted: widget.searchDateFormatted,
widget.title, onExport: () async {},
style: const TextStyle( isExport: false, // Set to false if export is not needed
fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
Center(
child: Text(
widget.searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
), ),
const SpaceHeight(16.0), 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( PieChart(
dataMap: dataMap2, dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800), animationDuration: Duration(milliseconds: 800),
@ -116,10 +116,13 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
), ),
// gradientList: ---To add gradient colors--- // gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient--- // 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( ),
mainAxisAlignment: MainAxisAlignment.end, child: Row(
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),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text( Text(
label, label,
style: TextStyle( style: const TextStyle(
color: isActive ? AppColors.primary : AppColors.black, fontSize: 16.0,
fontWeight: FontWeight.w600, 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(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
width: double.infinity,
height: context.deviceHeight * 0.1,
decoration: const BoxDecoration(
color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
'Report', 'Laporan',
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.black,
fontSize: 28, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
const SpaceHeight(4.0),
Text( Text(
DateTime.now().toFormattedDate(), DateTime.now().toFormattedDate2(),
style: const TextStyle( style: TextStyle(
color: AppColors.subtitle, color: AppColors.grey,
fontSize: 16, fontSize: 14,
), ),
), ),
const SpaceHeight(20.0),
const Divider(),
], ],
),
if (actionWidget != null)
Row(
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,7 +54,8 @@ 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) {
@ -75,7 +75,8 @@ class SummaryReportWidget extends StatelessWidget {
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,32 +27,12 @@ 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),
child: Column(
children: [ children: [
const SpaceHeight(24.0), ReportPageTitle(
Center( title: title,
child: Text( searchDateFormatted: searchDateFormatted,
title, onExport: () async {
style:
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
const SizedBox(
height: 8.0,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
GestureDetector(
onTap: () async {
try { try {
final status = await PermessionHelper().checkPermission(); final status = await PermessionHelper().checkPermission();
if (status) { if (status) {
@ -78,25 +58,6 @@ class TransactionReportWidget extends StatelessWidget {
); );
} }
}, },
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Expanded( Expanded(
@ -228,7 +189,6 @@ class TransactionReportWidget extends StatelessWidget {
), ),
), ),
], ],
),
); );
} }
} }

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