first commit

This commit is contained in:
Aditya Siregar
2025-07-30 22:38:44 +07:00
commit 73320561b0
444 changed files with 64633 additions and 0 deletions
@@ -0,0 +1,191 @@
import 'dart:developer';
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: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;
final String searchDateFormatted;
final List<ItemSales> itemSales;
final List<Widget>? headerWidgets;
const ItemSalesReportWidget({
super.key,
required this.itemSales,
required this.title,
required this.searchDateFormatted,
required this.headerWidgets,
});
@override
Widget build(BuildContext context) {
return Card(
color: const Color.fromARGB(255, 255, 255, 255),
child: 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 {
try {
final status = await PermessionHelper().checkPermission();
if (status) {
final pdfFile = await ItemSalesInvoice.generate(
itemSales, searchDateFormatted);
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: 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(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 80,
rightHandSideColumnWidth: 560,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 80,
height: 52,
alignment: Alignment.centerLeft,
child:
Center(child: Text(itemSales[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].orderId.toString())),
),
Container(
width: 160,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].productName!)),
),
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child:
Text(itemSales[index].quantity.toString())),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
itemSales[index].price!.currencyFormatRp,
)),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
(itemSales[index].price! *
itemSales[index].quantity!)
.currencyFormatRp,
)),
),
],
);
},
itemCount: itemSales.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55,
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,153 @@
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';
class PaymentMethodReportWidget extends StatelessWidget {
final PaymentMethodData paymentMethodData;
final String title;
final String searchDateFormatted;
final List<Widget> headerWidgets;
const PaymentMethodReportWidget({
super.key,
required this.paymentMethodData,
required this.title,
required this.searchDateFormatted,
required this.headerWidgets,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.white,
body: Column(
children: [
// HEADER
Container(
padding: const EdgeInsets.all(24.0),
decoration: const BoxDecoration(
color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.stroke,
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(8.0),
Text(
searchDateFormatted,
style: const TextStyle(
fontSize: 16,
color: AppColors.grey,
),
),
],
),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
'Total: ${paymentMethodData.total?.currencyFormatRpV2 ?? 'Rp 0'}',
style: const TextStyle(
color: AppColors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
),
// CONTENT
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
// TABLE HEADER
Row(
children: headerWidgets,
),
// TABLE BODY
...paymentMethodData.paymentMethods?.map((item) {
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.stroke,
width: 1,
),
),
),
child: Row(
children: [
_getBodyItemWidget(
item.paymentMethod ?? '-',
180,
),
_getBodyItemWidget(
item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0',
180,
),
_getBodyItemWidget(
item.transactionCount?.toString() ?? '0',
180,
),
],
),
);
}).toList() ??
[],
],
),
),
),
],
),
);
}
Widget _getBodyItemWidget(String label, double width) {
return Container(
width: width,
height: 56,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
label,
style: const TextStyle(
fontSize: 14,
),
),
);
}
}
@@ -0,0 +1,125 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'package:enaklo_pos/data/models/response/product_sales_response_model.dart';
class ProductSalesChartWidgets extends StatefulWidget {
final String title;
final String searchDateFormatted;
final List<ProductSales> productSales;
const ProductSalesChartWidgets({
super.key,
required this.title,
required this.searchDateFormatted,
required this.productSales,
});
@override
State<ProductSalesChartWidgets> createState() =>
_ProductSalesChartWidgetsState();
}
class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
Map<String, double> dataMap2 = {};
@override
void initState() {
loadData();
super.initState();
}
loadData() {
for (var data in widget.productSales) {
dataMap2[data.productName ?? 'Unknown'] =
double.parse(data.totalQuantity!);
}
}
final colorList = <Color>[
const Color(0xfffdcb6e),
const Color(0xff0984e3),
const Color(0xfffd79a8),
const Color(0xffe17055),
const Color(0xff6c5ce7),
const Color(0xfff0932b),
const Color(0xff6ab04c),
const Color(0xfff8a5c2),
const Color(0xffe84393),
const Color(0xfffd79a8),
const Color(0xffa29bfe),
const Color(0xff00b894),
const Color(0xffe17055),
const Color(0xffd63031),
const Color(0xffa29bfe),
const Color(0xff6c5ce7),
const Color(0xff00cec9),
const Color(0xfffad390),
const Color(0xff686de0),
const Color(0xfffdcb6e),
const Color(0xff0984e3),
const Color(0xfffd79a8),
const Color(0xffe17055),
const Color(0xff6c5ce7),
];
@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(
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),
),
),
const SpaceHeight(16.0),
PieChart(
dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800),
chartLegendSpacing: 32,
chartRadius: MediaQuery.of(context).size.width / 3.2,
colorList: colorList,
initialAngleInDegree: 0,
chartType: ChartType.disc,
ringStrokeWidth: 32,
// centerText: "HYBRID",
legendOptions: LegendOptions(
showLegendsInRow: false,
legendPosition: LegendPosition.right,
showLegends: true,
legendShape: BoxShape.circle,
legendTextStyle: TextStyle(
fontWeight: FontWeight.bold,
),
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
showChartValues: true,
showChartValuesInPercentage: false,
showChartValuesOutside: false,
decimalPlaces: 0,
),
// gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient---
)
],
),
),
);
}
}
@@ -0,0 +1,63 @@
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 VoidCallback onPressed;
final bool isActive;
const ReportMenu({
super.key,
required this.label,
required this.onPressed,
required this.isActive,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Container(
margin: const EdgeInsets.all(5.0),
width: 180.0,
height: 160.0,
alignment: Alignment.center,
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,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Assets.icons.report.svg(
colorFilter: isActive
? const ColorFilter.mode(
AppColors.primary,
BlendMode.srcIn,
)
: null,
),
const SpaceHeight(28.0),
Text(
label,
style: TextStyle(
color: isActive ? AppColors.primary : AppColors.black,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(24.0),
],
),
),
);
}
}
@@ -0,0 +1,38 @@
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});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Report',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(4.0),
Text(
DateTime.now().toFormattedDate(),
style: const TextStyle(
color: AppColors.subtitle,
fontSize: 16,
),
),
const SpaceHeight(20.0),
const Divider(),
],
);
}
}
@@ -0,0 +1,193 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:developer';
import 'package:enaklo_pos/core/components/dashed_line.dart';
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/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';
class SummaryReportWidget extends StatelessWidget {
final String title;
final String searchDateFormatted;
final SummaryModel summary;
const SummaryReportWidget({
super.key,
required this.title,
required this.searchDateFormatted,
required this.summary,
});
@override
Widget build(BuildContext context) {
return Card(
color: const Color.fromARGB(255, 255, 255, 255),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SpaceHeight(24.0),
Center(
child: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w800, fontSize: 16.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 {
log("PDF button clicked for summary report");
try {
log("Checking permissions...");
final status = await PermessionHelper().checkPermission();
log("Permission status: $status");
if (status) {
log("Permission granted, starting PDF generation...");
log("Summary data: ${summary.toMap()}");
log("Search date: $searchDateFormatted");
final pdfFile = await RevenueInvoice.generate(
summary, searchDateFormatted);
log("PDF file generated: ${pdfFile.path}");
log("PDF file exists: ${await pdfFile.exists()}");
log("PDF file size: ${await pdfFile.length()} bytes");
log("Attempting to open PDF file...");
await HelperPdfService.openFile(pdfFile);
log("PDF file opened successfully");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('PDF saved successfully: ${pdfFile.path}'),
backgroundColor: Colors.green,
),
);
} else {
log("Permission denied");
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");
log("Error stack trace: ${StackTrace.current}");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
),
const SpaceHeight(16.0),
Text(
'REVENUE : ${int.parse(summary.totalRevenue!).currencyFormatRp}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SpaceHeight(8.0),
const DashedLine(),
const DashedLine(),
const SpaceHeight(8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Subtotal'),
Text(
int.parse(summary.totalSubtotal!).currencyFormatRp,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SpaceHeight(4.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Discount'),
Text(
"- ${int.parse(summary.totalDiscount!.replaceAll('.00', '')).currencyFormatRp}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SpaceHeight(4.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Tax'),
Text(
"- ${int.parse(summary.totalTax!).currencyFormatRp}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SpaceHeight(4.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Service Charge'),
Text(
int.parse(summary.totalServiceCharge!).currencyFormatRp,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SpaceHeight(8.0),
const DashedLine(),
const DashedLine(),
const SpaceHeight(8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('TOTAL'),
Text(
summary.total!.currencyFormatRp,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
],
),
),
);
}
}
@@ -0,0 +1,234 @@
import 'dart:developer';
import 'package:enaklo_pos/core/components/spaces.dart';
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: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;
final String searchDateFormatted;
final List<ItemOrder> transactionReport;
final List<Widget>? headerWidgets;
const TransactionReportWidget({
super.key,
required this.transactionReport,
required this.title,
required this.searchDateFormatted,
required this.headerWidgets,
});
@override
Widget build(BuildContext context) {
return Card(
color: const Color.fromARGB(255, 255, 255, 255),
child: 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 {
try {
final status = await PermessionHelper().checkPermission();
if (status) {
final pdfFile = await TransactionSalesInvoice.generate(
transactionReport, searchDateFormatted);
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: 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(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 50,
rightHandSideColumnWidth: 1020,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 40,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].total!.currencyFormatRp,
)),
),
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].subTotal!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].tax!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
int.parse(transactionReport[index]
.discountAmount!
.replaceAll('.00', ''))
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index]
.serviceCharge!
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].totalItem.toString()),
),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].namaKasir!),
),
),
Container(
width: 230,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index]
.transactionTime!
.toFormattedDate()),
),
),
],
);
},
itemCount: transactionReport.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55,
),
),
),
),
],
),
);
}
}