feat: report transaction

This commit is contained in:
efrilm 2025-08-14 21:19:34 +07:00
parent 0c375de6e4
commit f37814fec8
16 changed files with 2239 additions and 14 deletions

View File

@ -0,0 +1,912 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
class TransactionReport {
static final primaryColor = PdfColor.fromHex("36175e");
static Future<File> previewPdf({
required Outlet outlet,
required String searchDateFormatted,
required CategoryAnalyticData? categoryAnalyticData,
required ProfitLossData? profitLossData,
required PaymentMethodAnalyticData? paymentMethodAnalyticData,
required ProductAnalyticData? productAnalyticData,
}) async {
final pdf = pw.Document();
final ByteData dataImage = await rootBundle.load('assets/logo/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List();
final profitLossProductSummary = {
'totalRevenue': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
0,
'totalCost': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.cost)) ??
0,
'totalGrossProfit': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.grossProfit)) ??
0,
'totalQuantity': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
0,
};
final categorySummary = {
'totalRevenue': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.totalRevenue)) ??
0,
'orderCount': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
0,
'productCount': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.productCount)) ??
0,
'totalQuantity': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.totalQuantity)) ??
0,
};
final productItemSummary = {
'totalRevenue': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
0,
'orderCount': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
0,
'totalQuantitySold': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
0,
};
// Membuat objek Image dari gambar
final image = pw.MemoryImage(bytes);
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: pw.EdgeInsets.zero,
build: (pw.Context context) {
return [
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Bagian kiri - Logo dan Info Perusahaan
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
pw.Container(
width: 40,
height: 40,
child: pw.Image(image),
),
pw.SizedBox(width: 15),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Apskel',
style: pw.TextStyle(
fontSize: 28,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 4),
pw.Text(
outlet.name ?? "",
style: pw.TextStyle(
fontSize: 16,
color: PdfColors.grey700,
),
),
pw.SizedBox(height: 2),
pw.Text(
outlet.address ?? "",
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey600,
),
),
],
),
],
),
// Bagian kanan - Info Laporan
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text(
'Laporan Transaksi',
style: pw.TextStyle(
fontSize: 24,
fontWeight: pw.FontWeight.bold,
color: PdfColors.grey800,
),
),
pw.SizedBox(height: 8),
pw.Text(
searchDateFormatted,
style: pw.TextStyle(
fontSize: 14,
color: PdfColors.grey600,
),
),
pw.SizedBox(height: 4),
pw.Text(
'Laporan',
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey500,
),
),
],
),
],
),
),
pw.Container(
width: double.infinity,
height: 3,
color: primaryColor,
),
// Summary
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('1. Ringkasan'),
pw.SizedBox(height: 30),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Penjualan (termasuk rasik)',
(profitLossData?.summary.totalRevenue ?? 0)
.toString()
.currencyFormatRpV2,
),
_buildSummaryItem(
'Total Total Terjual',
(profitLossData?.summary.totalOrders ?? 0)
.toString(),
),
_buildSummaryItem(
'HPP',
'${(profitLossData?.summary.totalCost ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalCost ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
),
_buildSummaryItem(
'Laba Kotor',
'${(profitLossData?.summary.grossProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.grossProfitMargin ?? 0).round()}%',
valueStyle: pw.TextStyle(
color: PdfColors.green800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
labelStyle: pw.TextStyle(
color: PdfColors.green800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
),
],
),
),
pw.SizedBox(width: 20),
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Biaya Lain lain',
'${(profitLossData?.summary.totalTax ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalTax ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
),
_buildSummaryItem(
'Laba/Rugi',
'${(profitLossData?.summary.netProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.netProfitMargin ?? 0).round()}%',
valueStyle: pw.TextStyle(
color: PdfColors.blue800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
labelStyle: pw.TextStyle(
color: PdfColors.blue800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
),
],
),
),
],
),
pw.SizedBox(height: 16),
pw.Text(
"Laba Rugi Perproduk",
style: pw.TextStyle(
fontSize: 16,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 20),
pw.Column(
children: [
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Produk'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Pendapatan'),
_buildHeaderCell('HPP'),
_buildHeaderCell('Laba Kotor'),
_buildHeaderCell('Margin (%)'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: profitLossData?.productData
.map(
(profitLoss) => _buildPerProductDataRow(
product: profitLoss.productName,
qty: profitLoss.quantitySold.toString(),
pendapatan: profitLoss.revenue
.toString()
.currencyFormatRpV2,
hpp: profitLoss.cost
.toString()
.currencyFormatRpV2,
labaKotor: profitLoss.grossProfit
.toString()
.currencyFormatRpV2,
margin:
'${profitLoss.grossProfitMargin.round()}%',
isEven: profitLossData.productData
.indexOf(profitLoss) %
2 ==
0,
),
)
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
profitLossProductSummary['totalQuantity']
.toString()),
_buildTotalCell(
profitLossProductSummary['totalRevenue']
.toString()
.currencyFormatRpV2),
_buildTotalCell(
profitLossProductSummary['totalCost']
.toString()
.currencyFormatRpV2),
_buildTotalCell(
profitLossProductSummary['totalGrossProfit']
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
// Summary Payment Method
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('2. Ringkasan Metode Pembayaran'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(1), // Tipe
2: pw.FlexColumnWidth(2.5), // Jumlah Order
3: pw.FlexColumnWidth(2), // Total Amount
4: pw.FlexColumnWidth(2), // Presentase
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Tipe'),
_buildHeaderCell('Jumlah Order'),
_buildHeaderCell('Total Amount'),
_buildHeaderCell('Presentase'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(1), // Tipe
2: pw.FlexColumnWidth(2.5), // Jumlah Order
3: pw.FlexColumnWidth(2), // Total Amount
4: pw.FlexColumnWidth(2), // Presentase
},
children: paymentMethodAnalyticData?.data
.map(
(payment) => _buildPaymentMethodDataRow(
name: payment.paymentMethodName,
tipe: payment.paymentMethodType
.toTitleCase(),
jumlahOrder:
payment.orderCount.toString(),
totalAmount: payment.totalAmount
.toString()
.currencyFormatRpV2,
presentase:
'${payment.percentage.round()}%',
isEven: paymentMethodAnalyticData.data
.indexOf(payment) %
2 ==
0,
),
)
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell((paymentMethodAnalyticData
?.summary.totalOrders ??
0)
.toString()),
_buildTotalCell((paymentMethodAnalyticData
?.summary.totalAmount ??
0)
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
// Summary Category
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('3. Ringkasan Kategori'),
pw.SizedBox(height: 30),
pw.Column(
children: [
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Total Produk'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Jumlah Order'),
_buildHeaderCell('Pendapatan'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: categoryAnalyticData?.data
.map((category) => _buildCategoryDataRow(
name: category.categoryName,
totalProduct:
category.productCount.toString(),
qty: category.totalQuantity.toString(),
jumlahOrder:
category.orderCount.toString(),
pendapatan: category.totalRevenue
.toString()
.currencyFormatRpV2,
isEven: categoryAnalyticData.data
.indexOf(category) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
categorySummary['productCount'].toString()),
_buildTotalCell(categorySummary['totalQuantity']
.toString()),
_buildTotalCell(
categorySummary['orderCount'].toString()),
_buildTotalCell(categorySummary['totalRevenue']
.toString()
.currencyFormatRpV2),
],
),
],
),
),
],
),
],
),
),
// Summary Item
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('4. Ringkasan Item'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Produk'),
_buildHeaderCell('Kategori'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Order'),
_buildHeaderCell('Pendapatan'),
_buildHeaderCell('Rata Rata'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: productAnalyticData?.data
.map((item) => _buildItemDataRow(
product: item.productName,
category: item.categoryName,
qty: item.quantitySold.toString(),
order: item.orderCount.toString(),
pendapatan: item.revenue
.toString()
.currencyFormatRpV2,
average: item.averagePrice
.round()
.toString()
.currencyFormatRpV2,
isEven: productAnalyticData.data
.indexOf(item) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell(
productItemSummary['totalQuantitySold']
.toString()),
_buildTotalCell(productItemSummary['orderCount']
.toString()),
_buildTotalCell(
productItemSummary['totalRevenue']
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
];
},
),
);
return HelperPdfService.saveDocument(
name: 'Laporan Transaksi | $searchDateFormatted.pdf', pdf: pdf);
}
static pw.Widget _buildSectionWidget(String title) {
return pw.Text(
title,
style: pw.TextStyle(
fontSize: 20,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
);
}
static pw.Widget _buildSummaryItem(
String label,
String value, {
pw.TextStyle? valueStyle,
pw.TextStyle? labelStyle,
}) {
return pw.Container(
padding: pw.EdgeInsets.only(bottom: 8),
margin: pw.EdgeInsets.only(bottom: 16),
decoration: pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.grey300,
),
),
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(label, style: labelStyle),
pw.Text(
value,
style: valueStyle ??
pw.TextStyle(
fontWeight: pw.FontWeight.bold,
),
),
],
),
);
}
static pw.Widget _buildHeaderCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.Widget _buildDataCell(String text,
{pw.Alignment alignment = pw.Alignment.center, PdfColor? textColor}) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
alignment: alignment,
child: pw.Text(
text,
style: pw.TextStyle(
fontSize: 12,
color: textColor ?? PdfColors.black,
fontWeight: pw.FontWeight.normal,
),
textAlign: alignment == pw.Alignment.centerLeft
? pw.TextAlign.left
: pw.TextAlign.center,
),
);
}
static pw.Widget _buildTotalCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.TableRow _buildPerProductDataRow({
required String product,
required String qty,
required String pendapatan,
required String hpp,
required String labaKotor,
required String margin,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
_buildDataCell(qty),
_buildDataCell(pendapatan),
_buildDataCell(hpp, textColor: PdfColors.red600),
_buildDataCell(labaKotor, textColor: PdfColors.green600),
_buildDataCell(margin),
],
);
}
static pw.TableRow _buildPaymentMethodDataRow({
required String name,
required String tipe,
required String jumlahOrder,
required String totalAmount,
required String presentase,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
_buildDataCell(tipe),
_buildDataCell(jumlahOrder),
_buildDataCell(totalAmount),
_buildDataCell(presentase),
],
);
}
static pw.TableRow _buildCategoryDataRow({
required String name,
required String totalProduct,
required String qty,
required String jumlahOrder,
required String pendapatan,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
_buildDataCell(totalProduct),
_buildDataCell(qty),
_buildDataCell(jumlahOrder),
_buildDataCell(pendapatan),
],
);
}
static pw.TableRow _buildItemDataRow({
required String product,
required String category,
required String qty,
required String order,
required String pendapatan,
required String average,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
_buildDataCell(category, alignment: pw.Alignment.centerLeft),
_buildDataCell(qty),
_buildDataCell(order),
_buildDataCell(pendapatan),
_buildDataCell(average),
],
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/network/dio_client.dart'; import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
@ -184,4 +185,38 @@ class AnalyticRemoteDatasource {
return left('Unexpected error occurred'); return left('Unexpected error occurred');
} }
} }
Future<Either<String, CategoryAnalyticResponseModel>> getCategory({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
final authData = await AuthLocalDataSource().getAuthData();
final headers = {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
};
try {
final response = await dio.get(
'${Variables.baseUrl}/api/v1/analytics/categories',
queryParameters: {
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
},
options: Options(headers: headers),
);
if (response.statusCode == 200) {
return right(CategoryAnalyticResponseModel.fromMap(response.data));
} else {
return left('Terjadi Kesalahan, Coba lagi nanti.');
}
} on DioException catch (e) {
log('Dio error: ${e.message}');
return left(e.response?.data.toString() ?? e.message ?? 'Unknown error');
} catch (e) {
log('Unexpected error: $e');
return left('Unexpected error occurred');
}
}
} }

View File

@ -0,0 +1,129 @@
class CategoryAnalyticResponseModel {
final bool success;
final CategoryAnalyticData? data;
final dynamic errors;
CategoryAnalyticResponseModel({
required this.success,
required this.data,
this.errors,
});
// Dari JSON String ke Model
factory CategoryAnalyticResponseModel.fromJson(Map<String, dynamic> json) {
return CategoryAnalyticResponseModel(
success: json['success'],
data: json['data'] == null
? null
: CategoryAnalyticData.fromMap(json['data']),
errors: json['errors'],
);
}
// Dari Model ke JSON String
Map<String, dynamic> toJson() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
// Dari Map ke Model
factory CategoryAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticResponseModel(
success: map['success'],
data: CategoryAnalyticData.fromMap(map['data']),
errors: map['errors'],
);
}
// Dari Model ke Map
Map<String, dynamic> toMap() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
}
class CategoryAnalyticData {
final String organizationId;
final String outletId;
final DateTime dateFrom;
final DateTime dateTo;
final List<CategoryAnalyticItem> data;
CategoryAnalyticData({
required this.organizationId,
required this.outletId,
required this.dateFrom,
required this.dateTo,
required this.data,
});
factory CategoryAnalyticData.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticData(
organizationId: map['organization_id'],
outletId: map['outlet_id'],
dateFrom: DateTime.parse(map['date_from']),
dateTo: DateTime.parse(map['date_to']),
data: map['data'] == null
? []
: List<CategoryAnalyticItem>.from(
map['data']?.map((x) => CategoryAnalyticItem.fromMap(x)) ?? [],
),
);
}
Map<String, dynamic> toMap() {
return {
'organization_id': organizationId,
'outlet_id': outletId,
'date_from': dateFrom.toIso8601String(),
'date_to': dateTo.toIso8601String(),
'data': data.map((x) => x.toMap()).toList(),
};
}
}
class CategoryAnalyticItem {
final String categoryId;
final String categoryName;
final int totalRevenue;
final int totalQuantity;
final int productCount;
final int orderCount;
CategoryAnalyticItem({
required this.categoryId,
required this.categoryName,
required this.totalRevenue,
required this.totalQuantity,
required this.productCount,
required this.orderCount,
});
factory CategoryAnalyticItem.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticItem(
categoryId: map['category_id'],
categoryName: map['category_name'],
totalRevenue: map['total_revenue'] ?? 0,
totalQuantity: map['total_quantity'] ?? 0,
productCount: map['product_count'] ?? 0,
orderCount: map['order_count'] ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
'category_id': categoryId,
'category_name': categoryName,
'total_revenue': totalRevenue,
'total_quantity': totalQuantity,
'product_count': productCount,
'order_count': orderCount,
};
}
}

View File

@ -1,6 +1,6 @@
class PaymentMethodAnalyticResponseModel { class PaymentMethodAnalyticResponseModel {
final bool success; final bool success;
final PaymentMethodAnalyticData data; final PaymentMethodAnalyticData? data;
final dynamic errors; final dynamic errors;
PaymentMethodAnalyticResponseModel({ PaymentMethodAnalyticResponseModel({
@ -18,7 +18,9 @@ class PaymentMethodAnalyticResponseModel {
factory PaymentMethodAnalyticResponseModel.fromMap(Map<String, dynamic> map) { factory PaymentMethodAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return PaymentMethodAnalyticResponseModel( return PaymentMethodAnalyticResponseModel(
success: map['success'], success: map['success'],
data: PaymentMethodAnalyticData.fromMap(map['data']), data: map['data'] == null
? null
: PaymentMethodAnalyticData.fromMap(map['data']),
errors: map['errors'], errors: map['errors'],
); );
} }
@ -26,7 +28,7 @@ class PaymentMethodAnalyticResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data.toMap(), 'data': data?.toMap(),
'errors': errors, 'errors': errors,
}; };
} }

View File

@ -1,6 +1,6 @@
class ProductAnalyticResponseModel { class ProductAnalyticResponseModel {
final bool success; final bool success;
final ProductAnalyticData data; final ProductAnalyticData? data;
final dynamic errors; final dynamic errors;
ProductAnalyticResponseModel({ ProductAnalyticResponseModel({
@ -17,7 +17,8 @@ class ProductAnalyticResponseModel {
factory ProductAnalyticResponseModel.fromMap(Map<String, dynamic> map) { factory ProductAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return ProductAnalyticResponseModel( return ProductAnalyticResponseModel(
success: map['success'] ?? false, success: map['success'] ?? false,
data: ProductAnalyticData.fromMap(map['data']), data:
map['data'] == null ? null : ProductAnalyticData.fromMap(map['data']),
errors: map['errors'], errors: map['errors'],
); );
} }
@ -25,7 +26,7 @@ class ProductAnalyticResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data.toMap(), 'data': data?.toMap(),
'errors': errors, 'errors': errors,
}; };
} }

View File

@ -1,6 +1,6 @@
class ProfitLossResponseModel { class ProfitLossResponseModel {
final bool success; final bool success;
final ProfitLossData data; final ProfitLossData? data;
final dynamic errors; final dynamic errors;
ProfitLossResponseModel({ ProfitLossResponseModel({
@ -13,7 +13,7 @@ class ProfitLossResponseModel {
factory ProfitLossResponseModel.fromJson(Map<String, dynamic> json) { factory ProfitLossResponseModel.fromJson(Map<String, dynamic> json) {
return ProfitLossResponseModel( return ProfitLossResponseModel(
success: json['success'], success: json['success'],
data: ProfitLossData.fromMap(json['data']), data: json['data'] == null ? null : ProfitLossData.fromMap(json['data']),
errors: json['errors'], errors: json['errors'],
); );
} }
@ -22,7 +22,7 @@ class ProfitLossResponseModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'success': success, 'success': success,
'data': data.toMap(), 'data': data?.toMap(),
'errors': errors, 'errors': errors,
}; };
} }
@ -40,7 +40,7 @@ class ProfitLossResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data.toMap(), 'data': data?.toMap(),
'errors': errors, 'errors': errors,
}; };
} }

View File

@ -17,6 +17,7 @@ import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_
import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart'; import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart'; import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/report/report_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
@ -288,6 +289,9 @@ class _MyAppState extends State<MyApp> {
BlocProvider( BlocProvider(
create: (context) => TransferTableBloc(TableRemoteDataSource()), create: (context) => TransferTableBloc(TableRemoteDataSource()),
), ),
BlocProvider(
create: (context) => ReportBloc(AnalyticRemoteDatasource()),
),
], ],
child: MaterialApp( child: MaterialApp(
navigatorKey: AuthInterceptor.navigatorKey, navigatorKey: AuthInterceptor.navigatorKey,

View File

@ -22,7 +22,7 @@ class PaymentMethodReportBloc
(l) => emit(_Error(l)), (l) => emit(_Error(l)),
(r) => emit( (r) => emit(
_Loaded( _Loaded(
r.data, r.data!,
), ),
), ),
); );

View File

@ -16,7 +16,7 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {
dateFrom: event.startDate, dateFrom: event.startDate,
dateTo: event.endDate, dateTo: event.endDate,
); );
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data))); result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!)));
}); });
} }
} }

View File

@ -0,0 +1,63 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'report_event.dart';
part 'report_state.dart';
part 'report_bloc.freezed.dart';
class ReportBloc extends Bloc<ReportEvent, ReportState> {
final AnalyticRemoteDatasource _datasource;
ReportBloc(this._datasource) : super(ReportState.initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final outlet = await OutletLocalDatasource().get();
final category = await _datasource.getCategory(
dateFrom: event.startDate, dateTo: event.endDate);
final product = await _datasource.getProduct(
dateFrom: event.startDate, dateTo: event.endDate);
final paymentMethod = await _datasource.getPaymentMethod(
dateFrom: event.startDate, dateTo: event.endDate);
final profitLoss = await _datasource.getProfitLoss(
dateFrom: event.startDate, dateTo: event.endDate);
if (category.isLeft() ||
product.isLeft() ||
paymentMethod.isLeft() ||
profitLoss.isLeft()) {
emit(_Error());
}
emit(_Loaded(
outlet,
category
.getOrElse(
() => CategoryAnalyticResponseModel(success: false, data: null))
.data!,
profitLoss
.getOrElse(
() => ProfitLossResponseModel(success: false, data: null))
.data,
paymentMethod
.getOrElse(() =>
PaymentMethodAnalyticResponseModel(success: false, data: null))
.data,
product
.getOrElse(
() => ProductAnalyticResponseModel(success: false, data: null))
.data,
));
});
}
}

View File

@ -0,0 +1,975 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'report_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ReportEvent {
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ReportEventCopyWith<ReportEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ReportEventCopyWith<$Res> {
factory $ReportEventCopyWith(
ReportEvent value, $Res Function(ReportEvent) then) =
_$ReportEventCopyWithImpl<$Res, ReportEvent>;
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class _$ReportEventCopyWithImpl<$Res, $Val extends ReportEvent>
implements $ReportEventCopyWith<$Res> {
_$ReportEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_value.copyWith(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res> implements $ReportEventCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$ReportEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_$GetImpl(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl({required this.startDate, required this.endDate});
@override
final DateTime startDate;
@override
final DateTime endDate;
@override
String toString() {
return 'ReportEvent.get(startDate: $startDate, endDate: $endDate)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$GetImpl &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate));
}
@override
int get hashCode => Object.hash(runtimeType, startDate, endDate);
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
__$$GetImplCopyWithImpl<_$GetImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) {
return get(startDate, endDate);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) {
return get?.call(startDate, endDate);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(startDate, endDate);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements ReportEvent {
const factory _Get(
{required final DateTime startDate,
required final DateTime endDate}) = _$GetImpl;
@override
DateTime get startDate;
@override
DateTime get endDate;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ReportState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ReportStateCopyWith<$Res> {
factory $ReportStateCopyWith(
ReportState value, $Res Function(ReportState) then) =
_$ReportStateCopyWithImpl<$Res, ReportState>;
}
/// @nodoc
class _$ReportStateCopyWithImpl<$Res, $Val extends ReportState>
implements $ReportStateCopyWith<$Res> {
_$ReportStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'ReportState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements ReportState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'ReportState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements ReportState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$LoadedImplCopyWith<$Res> {
factory _$$LoadedImplCopyWith(
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call(
{Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData});
}
/// @nodoc
class __$$LoadedImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$LoadedImpl>
implements _$$LoadedImplCopyWith<$Res> {
__$$LoadedImplCopyWithImpl(
_$LoadedImpl _value, $Res Function(_$LoadedImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? outlet = null,
Object? categoryAnalyticData = freezed,
Object? profitLossData = freezed,
Object? paymentMethodAnalyticData = freezed,
Object? productAnalyticData = freezed,
}) {
return _then(_$LoadedImpl(
null == outlet
? _value.outlet
: outlet // ignore: cast_nullable_to_non_nullable
as Outlet,
freezed == categoryAnalyticData
? _value.categoryAnalyticData
: categoryAnalyticData // ignore: cast_nullable_to_non_nullable
as CategoryAnalyticData?,
freezed == profitLossData
? _value.profitLossData
: profitLossData // ignore: cast_nullable_to_non_nullable
as ProfitLossData?,
freezed == paymentMethodAnalyticData
? _value.paymentMethodAnalyticData
: paymentMethodAnalyticData // ignore: cast_nullable_to_non_nullable
as PaymentMethodAnalyticData?,
freezed == productAnalyticData
? _value.productAnalyticData
: productAnalyticData // ignore: cast_nullable_to_non_nullable
as ProductAnalyticData?,
));
}
}
/// @nodoc
class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(
this.outlet,
this.categoryAnalyticData,
this.profitLossData,
this.paymentMethodAnalyticData,
this.productAnalyticData);
@override
final Outlet outlet;
@override
final CategoryAnalyticData? categoryAnalyticData;
@override
final ProfitLossData? profitLossData;
@override
final PaymentMethodAnalyticData? paymentMethodAnalyticData;
@override
final ProductAnalyticData? productAnalyticData;
@override
String toString() {
return 'ReportState.loaded(outlet: $outlet, categoryAnalyticData: $categoryAnalyticData, profitLossData: $profitLossData, paymentMethodAnalyticData: $paymentMethodAnalyticData, productAnalyticData: $productAnalyticData)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoadedImpl &&
(identical(other.outlet, outlet) || other.outlet == outlet) &&
(identical(other.categoryAnalyticData, categoryAnalyticData) ||
other.categoryAnalyticData == categoryAnalyticData) &&
(identical(other.profitLossData, profitLossData) ||
other.profitLossData == profitLossData) &&
(identical(other.paymentMethodAnalyticData,
paymentMethodAnalyticData) ||
other.paymentMethodAnalyticData == paymentMethodAnalyticData) &&
(identical(other.productAnalyticData, productAnalyticData) ||
other.productAnalyticData == productAnalyticData));
}
@override
int get hashCode => Object.hash(runtimeType, outlet, categoryAnalyticData,
profitLossData, paymentMethodAnalyticData, productAnalyticData);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
__$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return loaded(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return loaded?.call(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class _Loaded implements ReportState {
const factory _Loaded(
final Outlet outlet,
final CategoryAnalyticData? categoryAnalyticData,
final ProfitLossData? profitLossData,
final PaymentMethodAnalyticData? paymentMethodAnalyticData,
final ProductAnalyticData? productAnalyticData) = _$LoadedImpl;
Outlet get outlet;
CategoryAnalyticData? get categoryAnalyticData;
ProfitLossData? get profitLossData;
PaymentMethodAnalyticData? get paymentMethodAnalyticData;
ProductAnalyticData? get productAnalyticData;
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl();
@override
String toString() {
return 'ReportState.error()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$ErrorImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return error();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return error?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (error != null) {
return error();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements ReportState {
const factory _Error() = _$ErrorImpl;
}

View File

@ -0,0 +1,9 @@
part of 'report_bloc.dart';
@freezed
class ReportEvent with _$ReportEvent {
const factory ReportEvent.get({
required DateTime startDate,
required DateTime endDate,
}) = _Get;
}

View File

@ -0,0 +1,15 @@
part of 'report_bloc.dart';
@freezed
class ReportState with _$ReportState {
const factory ReportState.initial() = _Initial;
const factory ReportState.loading() = _Loading;
const factory ReportState.loaded(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData,
) = _Loaded;
const factory ReportState.error() = _Error;
}

View File

@ -1,7 +1,11 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
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/transaction_report.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart'; import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/report/report_bloc.dart';
import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart';
import 'package:enaklo_pos/presentation/report/widgets/profit_loss_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/profit_loss_widget.dart';
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart'; import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
@ -44,6 +48,9 @@ class _ReportPageState extends State<ReportPage> {
context.read<SummaryBloc>().add( context.read<SummaryBloc>().add(
SummaryEvent.getSummary(fromDate, toDate), SummaryEvent.getSummary(fromDate, toDate),
); );
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
} }
@override @override
@ -64,12 +71,14 @@ class _ReportPageState extends State<ReportPage> {
initialDate: fromDate, initialDate: fromDate,
onDateSelected: (selectedDate) { onDateSelected: (selectedDate) {
fromDate = selectedDate; fromDate = selectedDate;
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
setState(() {}); setState(() {});
}, },
), ),
), ),
const SpaceWidth(24.0), const SpaceWidth(12.0),
SizedBox( SizedBox(
width: 300, width: 300,
child: CustomDatePicker( child: CustomDatePicker(
@ -77,6 +86,9 @@ class _ReportPageState extends State<ReportPage> {
initialDate: toDate, initialDate: toDate,
onDateSelected: (selectedDate) { onDateSelected: (selectedDate) {
toDate = selectedDate; toDate = selectedDate;
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
setState(() {}); setState(() {});
// context.read<TransactionReportBloc>().add( // context.read<TransactionReportBloc>().add(
// TransactionReportEvent.getReport( // TransactionReportEvent.getReport(
@ -97,6 +109,72 @@ class _ReportPageState extends State<ReportPage> {
}, },
), ),
), ),
const SpaceWidth(12.0),
BlocBuilder<ReportBloc, ReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => SizedBox.shrink(),
loading: () => SizedBox(
height: 24,
width: 24,
child: const CircularProgressIndicator(),
),
loaded: (outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData) =>
InkWell(
onTap: () async {
try {
final status =
await PermessionHelper().checkPermission();
if (status) {
final pdfFile = await TransactionReport.previewPdf(
outlet: outlet,
searchDateFormatted: searchDateFormatted,
categoryAnalyticData: categoryAnalyticData,
profitLossData: profitLossData,
paymentMethodAnalyticData:
paymentMethodAnalyticData,
productAnalyticData: productAnalyticData,
);
log("pdfFile: $pdfFile");
await HelperPdfService.openFile(pdfFile);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Storage permission is required to save PDF'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(
color: AppColors.stroke,
)),
child: Icon(
Icons.download,
color: AppColors.primary,
size: 28,
),
),
),
);
},
),
], ],
), ),
Expanded( Expanded(

View File

@ -51,6 +51,8 @@ class ReportTitle extends StatelessWidget {
), ),
if (actionWidget != null) if (actionWidget != null)
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: actionWidget!, children: actionWidget!,
), ),
], ],