583 lines
27 KiB
Dart
583 lines
27 KiB
Dart
import 'dart:developer';
|
|
|
|
import 'package:enaklo_pos/core/components/date_range_picker.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/inventory_report/inventory_report_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/inventory_report_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:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
|
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
|
import 'package:enaklo_pos/core/utils/date_formatter.dart';
|
|
import 'package:enaklo_pos/presentation/report/blocs/item_sales_report/item_sales_report_bloc.dart';
|
|
import 'package:enaklo_pos/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart';
|
|
import 'package:enaklo_pos/presentation/report/blocs/product_sales/product_sales_bloc.dart';
|
|
import 'package:enaklo_pos/presentation/report/blocs/summary/summary_bloc.dart';
|
|
import 'package:enaklo_pos/presentation/report/blocs/transaction_report/transaction_report_bloc.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/item_sales_report_widget.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/payment_method_report_widget.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/product_analytic_widget.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/report_menu.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:enaklo_pos/presentation/report/widgets/transaction_report_widget.dart';
|
|
|
|
import '../../../core/components/spaces.dart';
|
|
|
|
class ReportPage extends StatefulWidget {
|
|
const ReportPage({super.key});
|
|
|
|
@override
|
|
State<ReportPage> createState() => _ReportPageState();
|
|
}
|
|
|
|
class _ReportPageState extends State<ReportPage> {
|
|
int selectedMenu = 0;
|
|
String title = 'Ringkasan Laporan Penjualan';
|
|
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
|
|
DateTime toDate = DateTime.now();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
context.read<SummaryBloc>().add(
|
|
SummaryEvent.getSummary(fromDate, toDate),
|
|
);
|
|
context.read<ReportBloc>().add(
|
|
ReportEvent.get(startDate: fromDate, endDate: toDate),
|
|
);
|
|
}
|
|
|
|
onDateChanged(DateTime? startDate, DateTime? endDate) {
|
|
setState(() {
|
|
fromDate = startDate ?? fromDate;
|
|
toDate = endDate ?? toDate;
|
|
});
|
|
context.read<ReportBloc>().add(
|
|
ReportEvent.get(startDate: fromDate, endDate: toDate),
|
|
);
|
|
if (selectedMenu == 0) {
|
|
context.read<SummaryBloc>().add(
|
|
SummaryEvent.getSummary(fromDate, toDate),
|
|
);
|
|
}
|
|
|
|
if (selectedMenu == 2) {
|
|
context.read<ItemSalesReportBloc>().add(
|
|
ItemSalesReportEvent.getItemSales(
|
|
startDate: fromDate, endDate: toDate),
|
|
);
|
|
}
|
|
|
|
if (selectedMenu == 3) {
|
|
context.read<ProductSalesBloc>().add(
|
|
ProductSalesEvent.getProductSales(
|
|
fromDate,
|
|
toDate,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (selectedMenu == 4) {
|
|
context.read<PaymentMethodReportBloc>().add(
|
|
PaymentMethodReportEvent.getPaymentMethodReport(
|
|
startDate: fromDate,
|
|
endDate: toDate,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (selectedMenu == 5) {
|
|
context.read<ProfitLossBloc>().add(
|
|
ProfitLossEvent.getProfitLoss(
|
|
fromDate,
|
|
toDate,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (selectedMenu == 6) {
|
|
context.read<InventoryReportBloc>().add(
|
|
InventoryReportEvent.get(
|
|
startDate: fromDate,
|
|
endDate: toDate,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
String searchDateFormatted =
|
|
'${fromDate.toFormattedDate2()} - ${toDate.toFormattedDate2()}';
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
body: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ReportTitle(
|
|
searchDateFormatted: searchDateFormatted,
|
|
actionWidget: [
|
|
InkWell(
|
|
onTap: () {
|
|
DateRangePickerModal.show(
|
|
context: context,
|
|
initialEndDate: toDate,
|
|
initialStartDate: fromDate,
|
|
primaryColor: AppColors.primary,
|
|
onChanged: onDateChanged,
|
|
);
|
|
},
|
|
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.calendar_month_outlined,
|
|
color: AppColors.primary,
|
|
size: 28,
|
|
),
|
|
),
|
|
),
|
|
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(
|
|
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: 'Ringkasan Laporan Penjualan',
|
|
subtitle:
|
|
'Ringkasan total penjualan dalam periode tertentu.',
|
|
icon: Icons.insert_drive_file_outlined,
|
|
onPressed: () {
|
|
selectedMenu = 0;
|
|
title = 'Ringkasan Laporan Penjualan';
|
|
setState(() {});
|
|
context.read<SummaryBloc>().add(
|
|
SummaryEvent.getSummary(fromDate, toDate),
|
|
);
|
|
|
|
log("Date ${DateFormatter.formatDateTime(fromDate)}");
|
|
},
|
|
isActive: selectedMenu == 0,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Transaksi',
|
|
subtitle:
|
|
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
|
|
icon: Icons.receipt_long_outlined,
|
|
onPressed: () {
|
|
context.push(SalesPage(status: 'completed'));
|
|
},
|
|
isActive: selectedMenu == 1,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Penjualan Item',
|
|
subtitle:
|
|
'Laporan penjualan berdasarkan masing-masing item atau produk.',
|
|
icon: Icons.inventory_2_outlined,
|
|
onPressed: () {
|
|
selectedMenu = 2;
|
|
title = 'Laporan Penjualan Item';
|
|
setState(() {});
|
|
context.read<ItemSalesReportBloc>().add(
|
|
ItemSalesReportEvent.getItemSales(
|
|
startDate: fromDate, endDate: toDate),
|
|
);
|
|
},
|
|
isActive: selectedMenu == 2,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Penjualan Produk',
|
|
subtitle:
|
|
'Laporan penjualan berdasarkan masing-masing produk.',
|
|
icon: Icons.bar_chart_outlined,
|
|
onPressed: () {
|
|
selectedMenu = 3;
|
|
title = 'Laporan Penjualan Produk';
|
|
setState(() {});
|
|
context.read<ProductSalesBloc>().add(
|
|
ProductSalesEvent.getProductSales(
|
|
fromDate,
|
|
toDate,
|
|
),
|
|
);
|
|
},
|
|
isActive: selectedMenu == 3,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Metode Pembayaran',
|
|
subtitle:
|
|
'Laporan metode pembayaran yang digunakan.',
|
|
icon: Icons.payment_outlined,
|
|
onPressed: () {
|
|
selectedMenu = 4;
|
|
title = 'Laporan Metode Pembayaran';
|
|
setState(() {});
|
|
context.read<PaymentMethodReportBloc>().add(
|
|
PaymentMethodReportEvent
|
|
.getPaymentMethodReport(
|
|
startDate: fromDate,
|
|
endDate: toDate,
|
|
),
|
|
);
|
|
},
|
|
isActive: selectedMenu == 4,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Untung Rugi',
|
|
subtitle: 'Laporan untung rugi penjualan.',
|
|
icon: Icons.trending_down,
|
|
onPressed: () {
|
|
selectedMenu = 5;
|
|
title = 'Laporan Untung Rugi';
|
|
setState(() {});
|
|
context.read<ProfitLossBloc>().add(
|
|
ProfitLossEvent.getProfitLoss(
|
|
fromDate,
|
|
toDate,
|
|
),
|
|
);
|
|
},
|
|
isActive: selectedMenu == 5,
|
|
),
|
|
ReportMenu(
|
|
label: 'Laporan Inventori',
|
|
subtitle: 'Laporan inventori produk',
|
|
icon: Icons.archive_outlined,
|
|
onPressed: () {
|
|
selectedMenu = 6;
|
|
title = 'Laporan Inventori';
|
|
setState(() {});
|
|
context.read<InventoryReportBloc>().add(
|
|
InventoryReportEvent.get(
|
|
startDate: fromDate,
|
|
endDate: toDate,
|
|
),
|
|
);
|
|
},
|
|
isActive: selectedMenu == 6,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// RIGHT CONTENT
|
|
Expanded(
|
|
flex: 4,
|
|
child: selectedMenu == 0
|
|
? BlocBuilder<SummaryBloc, SummaryState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
success: (data) {
|
|
return DashboardAnalyticWidget(
|
|
data: data,
|
|
title: title,
|
|
searchDateFormatted: searchDateFormatted,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 1
|
|
? BlocBuilder<TransactionReportBloc,
|
|
TransactionReportState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
loaded: (transactionReport) {
|
|
return TransactionReportWidget(
|
|
transactionReport: transactionReport,
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
headerWidgets:
|
|
_getTitleReportPageWidget(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 2
|
|
? BlocBuilder<ItemSalesReportBloc,
|
|
ItemSalesReportState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
loaded: (itemSales) {
|
|
return ItemSalesReportWidget(
|
|
sales: itemSales,
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
headerWidgets:
|
|
_getItemSalesPageWidget(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 3
|
|
? BlocBuilder<ProductSalesBloc,
|
|
ProductSalesState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child:
|
|
CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
success: (products) {
|
|
return ProductAnalyticsWidget(
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
productData: products,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 4
|
|
? BlocBuilder<PaymentMethodReportBloc,
|
|
PaymentMethodReportState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child:
|
|
CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
loaded: (paymentMethodData) {
|
|
return PaymentMethodReportWidget(
|
|
paymentMethodData:
|
|
paymentMethodData,
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
headerWidgets:
|
|
_getPaymentMethodPageWidget(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 5
|
|
? BlocBuilder<ProfitLossBloc,
|
|
ProfitLossState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () => const Center(
|
|
child:
|
|
CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
success: (data) {
|
|
return ProfitLossWidget(
|
|
data: data,
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: selectedMenu == 6
|
|
? BlocBuilder<
|
|
InventoryReportBloc,
|
|
InventoryReportState>(
|
|
builder: (context, state) {
|
|
return state.maybeWhen(
|
|
orElse: () =>
|
|
const Center(
|
|
child:
|
|
CircularProgressIndicator(),
|
|
),
|
|
error: (message) {
|
|
return Text(message);
|
|
},
|
|
loaded: (data) {
|
|
return InventoryReportWidget(
|
|
title: title,
|
|
searchDateFormatted:
|
|
searchDateFormatted,
|
|
inventory: data,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
: const SizedBox.shrink()),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _getTitleReportPageWidget() {
|
|
return [
|
|
_getTitleItemWidget('ID', 120),
|
|
_getTitleItemWidget('Total', 100),
|
|
_getTitleItemWidget('Sub Total', 100),
|
|
_getTitleItemWidget('Tax', 100),
|
|
_getTitleItemWidget('Disocunt', 100),
|
|
_getTitleItemWidget('Service', 100),
|
|
_getTitleItemWidget('Total Item', 100),
|
|
_getTitleItemWidget('Cashier', 180),
|
|
_getTitleItemWidget('Time', 200),
|
|
];
|
|
}
|
|
|
|
List<Widget> _getItemSalesPageWidget() {
|
|
return [
|
|
_getTitleItemWidget('ID', 80),
|
|
_getTitleItemWidget('Order', 100),
|
|
_getTitleItemWidget('Product', 200),
|
|
_getTitleItemWidget('Qty', 60),
|
|
_getTitleItemWidget('Price', 150),
|
|
_getTitleItemWidget('Total Price', 160),
|
|
];
|
|
}
|
|
|
|
List<Widget> _getPaymentMethodPageWidget() {
|
|
return [
|
|
_getTitleItemWidget('Payment Method', 180),
|
|
_getTitleItemWidget('Total Amount', 180),
|
|
_getTitleItemWidget('Transaction Count', 180),
|
|
];
|
|
}
|
|
|
|
Widget _getTitleItemWidget(String label, double width) {
|
|
return Container(
|
|
width: width,
|
|
height: 56,
|
|
color: AppColors.primary,
|
|
alignment: Alignment.centerLeft,
|
|
child: Center(
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|