feat: report page
This commit is contained in:
parent
8e4a289625
commit
e825e5daed
@ -11,7 +11,7 @@
|
||||
<!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<application
|
||||
android:label="EnakloPOS"
|
||||
android:label="ApskelPOS"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
|
||||
@ -5,7 +5,7 @@ import 'lib/core/utils/app_icon_generator.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
print('Generating EnakloPOS app icon...');
|
||||
print('Generating ApskelPOS app icon...');
|
||||
|
||||
try {
|
||||
final iconData = await AppIconGenerator.generateAppIcon();
|
||||
@ -20,14 +20,14 @@ void main() async {
|
||||
final iconFile = File('assets/logo/logo_app_icon.png');
|
||||
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(' - White background for visibility');
|
||||
print(' - Blue circular background');
|
||||
print(' - Gift box with "e" inside');
|
||||
print(' - "ENAKLO" and "POS" text');
|
||||
print(' - 1024x1024 resolution for high quality');
|
||||
|
||||
} catch (e) {
|
||||
print('❌ Error generating app icon: $e');
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>EnakloPOS</string>
|
||||
<string>ApskelPOS</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@ -38,7 +38,7 @@ class ItemSalesInvoice {
|
||||
|
||||
return HelperPdfService.saveDocument(
|
||||
name:
|
||||
'Enaklo POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
'Apskel POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
pdf: pdf);
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ class ItemSalesInvoice {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 1 * PdfPageFormat.cm),
|
||||
Text('Enaklo POS | Item Sales Report',
|
||||
Text('Apskel POS | Item Sales Report',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@ -28,7 +28,8 @@ class RevenueInvoice {
|
||||
|
||||
// Load 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 image = pw.MemoryImage(bytes);
|
||||
log("Logo image loaded successfully, size: ${bytes.length} bytes");
|
||||
@ -49,7 +50,7 @@ class RevenueInvoice {
|
||||
log("Saving PDF document...");
|
||||
return HelperPdfService.saveDocument(
|
||||
name:
|
||||
'Enaklo POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
pdf: pdf,
|
||||
);
|
||||
} catch (e) {
|
||||
@ -69,7 +70,7 @@ class RevenueInvoice {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 1 * PdfPageFormat.cm),
|
||||
Text('Enaklo POS | Summary Sales Report',
|
||||
Text('Apskel POS | Summary Sales Report',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -125,7 +126,8 @@ class RevenueInvoice {
|
||||
buildText(
|
||||
title: 'Discount',
|
||||
titleStyle: TextStyle(fontWeight: FontWeight.normal),
|
||||
value: "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
|
||||
value:
|
||||
"- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
|
||||
unite: true,
|
||||
textStyle: TextStyle(
|
||||
color: PdfColor.fromHex('#FF0000'),
|
||||
@ -147,7 +149,8 @@ class RevenueInvoice {
|
||||
titleStyle: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
value: safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
|
||||
value:
|
||||
safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
|
||||
unite: true,
|
||||
),
|
||||
Divider(),
|
||||
|
||||
@ -38,7 +38,7 @@ class TransactionSalesInvoice {
|
||||
|
||||
return HelperPdfService.saveDocument(
|
||||
name:
|
||||
'Enaklo POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
'Apskel POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
pdf: pdf);
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ class TransactionSalesInvoice {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 1 * PdfPageFormat.cm),
|
||||
Text('Enaklo POS | Transaction Sales Report',
|
||||
Text('Apskel POS | Transaction Sales Report',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@ -35,7 +35,7 @@ class PrintDataoutputs {
|
||||
final total = totalPrice + pajak;
|
||||
|
||||
bytes += generator.reset();
|
||||
bytes += generator.text('Enaklo POS',
|
||||
bytes += generator.text('Apskel POS',
|
||||
styles: const PosStyles(
|
||||
bold: true,
|
||||
align: PosAlign.center,
|
||||
@ -202,7 +202,7 @@ class PrintDataoutputs {
|
||||
// bytes += generator.feed(3);
|
||||
// }
|
||||
|
||||
bytes += generator.text('Enaklo POS',
|
||||
bytes += generator.text('Apskel POS',
|
||||
styles: const PosStyles(
|
||||
bold: true,
|
||||
align: PosAlign.center,
|
||||
@ -487,8 +487,8 @@ class PrintDataoutputs {
|
||||
String namaKasir,
|
||||
String customerName,
|
||||
int paper,
|
||||
{int taxPercentage = 11, int serviceChargePercentage = 5}
|
||||
) async {
|
||||
{int taxPercentage = 11,
|
||||
int serviceChargePercentage = 5}) async {
|
||||
List<int> bytes = [];
|
||||
|
||||
final profile = await CapabilityProfile.load();
|
||||
@ -778,8 +778,13 @@ class PrintDataoutputs {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Future<List<int>> printChecker(List<ProductQuantity> products,
|
||||
String tableName, String draftName, String cashierName, int paper, String orderType) async {
|
||||
Future<List<int>> printChecker(
|
||||
List<ProductQuantity> products,
|
||||
String tableName,
|
||||
String draftName,
|
||||
String cashierName,
|
||||
int paper,
|
||||
String orderType) async {
|
||||
List<int> bytes = [];
|
||||
|
||||
final profile = await CapabilityProfile.load();
|
||||
@ -908,8 +913,13 @@ class PrintDataoutputs {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Future<List<int>> printKitchen(List<ProductQuantity> products,
|
||||
String tableNumber, String draftName, String cashierName, int paper, String orderType) async {
|
||||
Future<List<int>> printKitchen(
|
||||
List<ProductQuantity> products,
|
||||
String tableNumber,
|
||||
String draftName,
|
||||
String cashierName,
|
||||
int paper,
|
||||
String orderType) async {
|
||||
List<int> bytes = [];
|
||||
|
||||
final profile = await CapabilityProfile.load();
|
||||
|
||||
@ -49,7 +49,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
const SpaceHeight(24.0),
|
||||
const Center(
|
||||
child: Text(
|
||||
'Enaklo POS',
|
||||
'Apskel POS',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
@ -34,28 +34,29 @@ class _ReportPageState extends State<ReportPage> {
|
||||
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
|
||||
DateTime toDate = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.read<TransactionReportBloc>().add(
|
||||
TransactionReportEvent.getReport(
|
||||
startDate: DateFormatter.formatDateTime(fromDate),
|
||||
endDate: DateFormatter.formatDateTime(toDate)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String searchDateFormatted =
|
||||
'${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}';
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
// LEFT CONTENT
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
backgroundColor: AppColors.background,
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ReportTitle(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
ReportTitle(
|
||||
actionWidget: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: CustomDatePicker(
|
||||
prefix: const Text('From: '),
|
||||
initialDate: fromDate,
|
||||
@ -67,7 +68,8 @@ class _ReportPageState extends State<ReportPage> {
|
||||
),
|
||||
),
|
||||
const SpaceWidth(24.0),
|
||||
Flexible(
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: CustomDatePicker(
|
||||
prefix: const Text('To: '),
|
||||
initialDate: toDate,
|
||||
@ -95,20 +97,34 @@ class _ReportPageState extends State<ReportPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Wrap(
|
||||
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: 'Transaction Report',
|
||||
label: 'Laporan Transaksi',
|
||||
subtitle:
|
||||
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
|
||||
icon: Icons.receipt_long_outlined,
|
||||
onPressed: () {
|
||||
selectedMenu = 0;
|
||||
title = 'Transaction Report';
|
||||
title = 'Laporan Transaksi';
|
||||
setState(() {});
|
||||
//enddate is 1 month before the current date
|
||||
context.read<TransactionReportBloc>().add(
|
||||
TransactionReportEvent.getReport(
|
||||
startDate: DateFormatter.formatDateTime(
|
||||
startDate:
|
||||
DateFormatter.formatDateTime(
|
||||
fromDate),
|
||||
endDate: DateFormatter.formatDateTime(
|
||||
toDate)),
|
||||
@ -117,14 +133,18 @@ class _ReportPageState extends State<ReportPage> {
|
||||
isActive: selectedMenu == 0,
|
||||
),
|
||||
ReportMenu(
|
||||
label: 'Item Sales Report',
|
||||
label: 'Laporan Penjualan Item',
|
||||
subtitle:
|
||||
'Laporan penjualan berdasarkan masing-masing item atau produk.',
|
||||
icon: Icons.inventory_2_outlined,
|
||||
onPressed: () {
|
||||
selectedMenu = 1;
|
||||
title = 'Item Sales Report';
|
||||
title = 'Laporan Penjualan Item';
|
||||
setState(() {});
|
||||
context.read<ItemSalesReportBloc>().add(
|
||||
ItemSalesReportEvent.getItemSales(
|
||||
startDate: DateFormatter.formatDateTime(
|
||||
startDate:
|
||||
DateFormatter.formatDateTime(
|
||||
fromDate),
|
||||
endDate: DateFormatter.formatDateTime(
|
||||
toDate)),
|
||||
@ -133,28 +153,36 @@ class _ReportPageState extends State<ReportPage> {
|
||||
isActive: selectedMenu == 1,
|
||||
),
|
||||
ReportMenu(
|
||||
label: 'Product Sales Chart',
|
||||
label: 'Chart Penjualan Produk',
|
||||
subtitle:
|
||||
'Grafik visual penjualan produk untuk analisa performa penjualan.',
|
||||
icon: Icons.bar_chart_outlined,
|
||||
onPressed: () {
|
||||
selectedMenu = 2;
|
||||
title = 'Product Sales Chart';
|
||||
title = 'Chart Penjualan Produk';
|
||||
setState(() {});
|
||||
context.read<ProductSalesBloc>().add(
|
||||
ProductSalesEvent.getProductSales(
|
||||
DateFormatter.formatDateTime(fromDate),
|
||||
DateFormatter.formatDateTime(
|
||||
fromDate),
|
||||
DateFormatter.formatDateTime(toDate)),
|
||||
);
|
||||
},
|
||||
isActive: selectedMenu == 2,
|
||||
),
|
||||
ReportMenu(
|
||||
label: 'Summary Sales Report',
|
||||
label: 'Ringkasan Laporan Penjualan',
|
||||
subtitle:
|
||||
'Ringkasan total penjualan dalam periode tertentu.',
|
||||
icon: Icons.insert_drive_file_outlined,
|
||||
onPressed: () {
|
||||
selectedMenu = 3;
|
||||
title = 'Summary Sales Report';
|
||||
title = 'Ringkasan Laporan Penjualan';
|
||||
setState(() {});
|
||||
context.read<SummaryBloc>().add(
|
||||
SummaryEvent.getSummary(
|
||||
DateFormatter.formatDateTime(fromDate),
|
||||
DateFormatter.formatDateTime(
|
||||
fromDate),
|
||||
DateFormatter.formatDateTime(toDate)),
|
||||
);
|
||||
|
||||
@ -163,15 +191,23 @@ class _ReportPageState extends State<ReportPage> {
|
||||
isActive: selectedMenu == 3,
|
||||
),
|
||||
ReportMenu(
|
||||
label: 'Payment Method Report',
|
||||
label: 'Laporan Metode Pembayaran',
|
||||
subtitle:
|
||||
'Laporan metode pembayaran yang digunakan.',
|
||||
icon: Icons.payment_outlined,
|
||||
onPressed: () {
|
||||
selectedMenu = 4;
|
||||
title = 'Payment Method Report';
|
||||
title = 'Laporan Metode Pembayaran';
|
||||
setState(() {});
|
||||
context.read<PaymentMethodReportBloc>().add(
|
||||
PaymentMethodReportEvent.getPaymentMethodReport(
|
||||
startDate: DateFormatter.formatDateTime(fromDate),
|
||||
endDate: DateFormatter.formatDateTime(toDate)),
|
||||
PaymentMethodReportEvent
|
||||
.getPaymentMethodReport(
|
||||
startDate:
|
||||
DateFormatter.formatDateTime(
|
||||
fromDate),
|
||||
endDate:
|
||||
DateFormatter.formatDateTime(
|
||||
toDate)),
|
||||
);
|
||||
},
|
||||
isActive: selectedMenu == 4,
|
||||
@ -179,17 +215,16 @@ class _ReportPageState extends State<ReportPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// RIGHT CONTENT
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: 4,
|
||||
child: selectedMenu == 0
|
||||
? BlocBuilder<TransactionReportBloc, TransactionReportState>(
|
||||
? BlocBuilder<TransactionReportBloc,
|
||||
TransactionReportState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
@ -210,7 +245,8 @@ class _ReportPageState extends State<ReportPage> {
|
||||
},
|
||||
)
|
||||
: selectedMenu == 1
|
||||
? BlocBuilder<ItemSalesReportBloc, ItemSalesReportState>(
|
||||
? BlocBuilder<ItemSalesReportBloc,
|
||||
ItemSalesReportState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
@ -223,15 +259,18 @@ class _ReportPageState extends State<ReportPage> {
|
||||
return ItemSalesReportWidget(
|
||||
itemSales: itemSales,
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
headerWidgets: _getItemSalesPageWidget(),
|
||||
searchDateFormatted:
|
||||
searchDateFormatted,
|
||||
headerWidgets:
|
||||
_getItemSalesPageWidget(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: selectedMenu == 2
|
||||
? BlocBuilder<ProductSalesBloc, ProductSalesState>(
|
||||
? BlocBuilder<ProductSalesBloc,
|
||||
ProductSalesState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
@ -243,7 +282,8 @@ class _ReportPageState extends State<ReportPage> {
|
||||
success: (productSales) {
|
||||
return ProductSalesChartWidgets(
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
searchDateFormatted:
|
||||
searchDateFormatted,
|
||||
productSales: productSales,
|
||||
);
|
||||
},
|
||||
@ -255,7 +295,8 @@ class _ReportPageState extends State<ReportPage> {
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
error: (message) {
|
||||
return Text(message);
|
||||
@ -264,28 +305,34 @@ class _ReportPageState extends State<ReportPage> {
|
||||
return SummaryReportWidget(
|
||||
summary: summary,
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
searchDateFormatted:
|
||||
searchDateFormatted,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: selectedMenu == 4
|
||||
? BlocBuilder<PaymentMethodReportBloc, PaymentMethodReportState>(
|
||||
? BlocBuilder<PaymentMethodReportBloc,
|
||||
PaymentMethodReportState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
error: (message) {
|
||||
return Text(message);
|
||||
},
|
||||
loaded: (paymentMethodData) {
|
||||
return PaymentMethodReportWidget(
|
||||
paymentMethodData: paymentMethodData,
|
||||
paymentMethodData:
|
||||
paymentMethodData,
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
headerWidgets: _getPaymentMethodPageWidget(),
|
||||
searchDateFormatted:
|
||||
searchDateFormatted,
|
||||
headerWidgets:
|
||||
_getPaymentMethodPageWidget(),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -294,6 +341,9 @@ class _ReportPageState extends State<ReportPage> {
|
||||
: const SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -314,11 +364,11 @@ class _ReportPageState extends State<ReportPage> {
|
||||
List<Widget> _getItemSalesPageWidget() {
|
||||
return [
|
||||
_getTitleItemWidget('ID', 80),
|
||||
_getTitleItemWidget('Order', 60),
|
||||
_getTitleItemWidget('Product', 160),
|
||||
_getTitleItemWidget('Order', 100),
|
||||
_getTitleItemWidget('Product', 200),
|
||||
_getTitleItemWidget('Qty', 60),
|
||||
_getTitleItemWidget('Price', 140),
|
||||
_getTitleItemWidget('Total Price', 140),
|
||||
_getTitleItemWidget('Price', 150),
|
||||
_getTitleItemWidget('Total Price', 160),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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/extensions/int_ext.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:enaklo_pos/core/utils/item_sales_invoice.dart';
|
||||
import 'package:enaklo_pos/core/utils/permession_handler.dart';
|
||||
import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart';
|
||||
import 'package:horizontal_data_table/horizontal_data_table.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class ItemSalesReportWidget extends StatelessWidget {
|
||||
final String title;
|
||||
@ -26,32 +26,12 @@ class ItemSalesReportWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
child: Column(
|
||||
return Column(
|
||||
children: [
|
||||
const SpaceHeight(24.0),
|
||||
Center(
|
||||
child: Text(
|
||||
title,
|
||||
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 {
|
||||
ReportPageTitle(
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
onExport: () async {
|
||||
try {
|
||||
final status = await PermessionHelper().checkPermission();
|
||||
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),
|
||||
Expanded(
|
||||
@ -105,7 +66,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: HorizontalDataTable(
|
||||
leftHandSideColumnWidth: 80,
|
||||
rightHandSideColumnWidth: 560,
|
||||
rightHandSideColumnWidth: 670,
|
||||
isFixedHeader: true,
|
||||
headerWidgets: headerWidgets,
|
||||
|
||||
@ -116,37 +77,35 @@ class ItemSalesReportWidget extends StatelessWidget {
|
||||
width: 80,
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
Center(child: Text(itemSales[index].id.toString())),
|
||||
child: Center(child: Text(itemSales[index].id.toString())),
|
||||
);
|
||||
},
|
||||
rightSideItemBuilder: (context, index) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 60,
|
||||
width: 100,
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Text(itemSales[index].orderId.toString())),
|
||||
),
|
||||
Container(
|
||||
width: 160,
|
||||
width: 200,
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Text(itemSales[index].productName!)),
|
||||
child:
|
||||
Center(child: Text(itemSales[index].productName!)),
|
||||
),
|
||||
Container(
|
||||
width: 60,
|
||||
height: 52,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child:
|
||||
Text(itemSales[index].quantity.toString())),
|
||||
child: Text(itemSales[index].quantity.toString())),
|
||||
),
|
||||
Container(
|
||||
width: 140,
|
||||
width: 150,
|
||||
height: 52,
|
||||
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
|
||||
alignment: Alignment.centerLeft,
|
||||
@ -156,14 +115,13 @@ class ItemSalesReportWidget extends StatelessWidget {
|
||||
)),
|
||||
),
|
||||
Container(
|
||||
width: 140,
|
||||
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!)
|
||||
(itemSales[index].price! * itemSales[index].quantity!)
|
||||
.currencyFormatRp,
|
||||
)),
|
||||
),
|
||||
@ -185,7 +143,6 @@ class ItemSalesReportWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.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/data/models/response/payment_method_response_model.dart';
|
||||
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
|
||||
|
||||
import '../../../core/components/spaces.dart';
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
|
||||
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:pie_chart/pie_chart.dart';
|
||||
|
||||
@ -67,27 +68,26 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
|
||||
child: Column(
|
||||
return Column(
|
||||
children: [
|
||||
const SpaceHeight(24.0),
|
||||
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),
|
||||
),
|
||||
ReportPageTitle(
|
||||
title: widget.title,
|
||||
searchDateFormatted: widget.searchDateFormatted,
|
||||
onExport: () async {},
|
||||
isExport: false, // Set to false if export is not needed
|
||||
),
|
||||
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),
|
||||
@ -116,10 +116,13 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
|
||||
),
|
||||
// gradientList: ---To add gradient colors---
|
||||
// emptyColorGradient: ---Empty Color gradient---
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:enaklo_pos/core/assets/assets.gen.dart';
|
||||
|
||||
import '../../../core/components/spaces.dart';
|
||||
import '../../../core/constants/colors.dart';
|
||||
|
||||
|
||||
|
||||
class ReportMenu extends StatelessWidget {
|
||||
final String label;
|
||||
final String subtitle;
|
||||
final IconData icon;
|
||||
final VoidCallback onPressed;
|
||||
final bool isActive;
|
||||
|
||||
@ -16,6 +15,8 @@ class ReportMenu extends StatelessWidget {
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
required this.isActive,
|
||||
required this.subtitle,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -23,38 +24,43 @@ class ReportMenu extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(5.0),
|
||||
width: 180.0,
|
||||
height: 160.0,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive ? AppColors.primary.withOpacity(0.13) : AppColors.white,
|
||||
borderRadius: BorderRadius.circular(18.0),
|
||||
border: Border.all(
|
||||
color: isActive ? AppColors.primary : AppColors.stroke,
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: isActive ? AppColors.primary : Colors.transparent,
|
||||
width: 4.0,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Assets.icons.report.svg(
|
||||
colorFilter: isActive
|
||||
? const ColorFilter.mode(
|
||||
AppColors.primary,
|
||||
BlendMode.srcIn,
|
||||
)
|
||||
: null,
|
||||
Icon(
|
||||
icon,
|
||||
size: 24.0,
|
||||
color: isActive ? AppColors.primary : AppColors.grey,
|
||||
),
|
||||
const SpaceHeight(28.0),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isActive ? AppColors.primary : AppColors.black,
|
||||
fontWeight: FontWeight.w600,
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
65
lib/presentation/report/widgets/report_page_title.dart
Normal file
65
lib/presentation/report/widgets/report_page_title.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,38 +1,60 @@
|
||||
import 'package:enaklo_pos/core/extensions/build_context_ext.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 '../../../core/constants/colors.dart';
|
||||
|
||||
|
||||
|
||||
class ReportTitle extends StatelessWidget {
|
||||
const ReportTitle({super.key});
|
||||
final List<Widget>? actionWidget;
|
||||
const ReportTitle({super.key, this.actionWidget});
|
||||
|
||||
@override
|
||||
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,
|
||||
children: [
|
||||
const Text(
|
||||
'Report',
|
||||
'Laporan',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 28,
|
||||
color: AppColors.black,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SpaceHeight(4.0),
|
||||
Text(
|
||||
DateTime.now().toFormattedDate(),
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtitle,
|
||||
fontSize: 16,
|
||||
DateTime.now().toFormattedDate2(),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SpaceHeight(20.0),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
if (actionWidget != null)
|
||||
Row(
|
||||
children: actionWidget!,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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/data/models/response/summary_response_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import '../../../core/utils/revenue_invoice.dart';
|
||||
|
||||
@ -55,7 +54,8 @@ class SummaryReportWidget extends StatelessWidget {
|
||||
log("PDF button clicked for summary report");
|
||||
try {
|
||||
log("Checking permissions...");
|
||||
final status = await PermessionHelper().checkPermission();
|
||||
final status =
|
||||
await PermessionHelper().checkPermission();
|
||||
log("Permission status: $status");
|
||||
|
||||
if (status) {
|
||||
@ -75,7 +75,8 @@ class SummaryReportWidget extends StatelessWidget {
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('PDF saved successfully: ${pdfFile.path}'),
|
||||
content: Text(
|
||||
'PDF saved successfully: ${pdfFile.path}'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
@ -83,7 +84,8 @@ class SummaryReportWidget extends StatelessWidget {
|
||||
log("Permission denied");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Storage permission is required to save PDF'),
|
||||
content: Text(
|
||||
'Storage permission is required to save PDF'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
|
||||
@ -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/int_ext.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:enaklo_pos/core/utils/permession_handler.dart';
|
||||
import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart';
|
||||
import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart';
|
||||
import 'package:horizontal_data_table/horizontal_data_table.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class TransactionReportWidget extends StatelessWidget {
|
||||
final String title;
|
||||
@ -27,32 +27,12 @@ class TransactionReportWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
child: Column(
|
||||
return Column(
|
||||
children: [
|
||||
const SpaceHeight(24.0),
|
||||
Center(
|
||||
child: Text(
|
||||
title,
|
||||
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 {
|
||||
ReportPageTitle(
|
||||
title: title,
|
||||
searchDateFormatted: searchDateFormatted,
|
||||
onExport: () async {
|
||||
try {
|
||||
final status = await PermessionHelper().checkPermission();
|
||||
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),
|
||||
Expanded(
|
||||
@ -228,7 +189,6 @@ class TransactionReportWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ class _SalesPageState extends State<SalesPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Enaklo POS ',
|
||||
'Apskel POS ',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 22,
|
||||
|
||||
@ -171,8 +171,7 @@ class _ManagePrinterPageState extends State<ManagePrinterPage> {
|
||||
//bytes += generator.setGlobalFont(PosFontType.fontA);
|
||||
bytes += generator.reset();
|
||||
|
||||
bytes +=
|
||||
generator.text('Enaklo POS', styles: const PosStyles(bold: true));
|
||||
bytes += generator.text('Apskel POS', styles: const PosStyles(bold: true));
|
||||
bytes +=
|
||||
generator.text('Reverse text', styles: const PosStyles(reverse: true));
|
||||
bytes += generator.text('Underlined text',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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
|
||||
# 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user