640 lines
30 KiB
Dart
Raw Normal View History

2025-07-30 22:38:44 +07:00
import 'dart:developer';
2025-08-15 16:09:25 +07:00
import 'package:enaklo_pos/core/components/date_range_picker.dart';
2025-08-06 12:06:34 +07:00
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
2025-08-14 21:19:34 +07:00
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';
2025-08-15 17:07:29 +07:00
import 'package:enaklo_pos/presentation/report/blocs/category_report/category_report_bloc.dart';
2025-08-15 01:12:04 +07:00
import 'package:enaklo_pos/presentation/report/blocs/inventory_report/inventory_report_bloc.dart';
2025-08-06 13:38:49 +07:00
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
2025-08-14 21:19:34 +07:00
import 'package:enaklo_pos/presentation/report/blocs/report/report_bloc.dart';
2025-08-15 17:07:29 +07:00
import 'package:enaklo_pos/presentation/report/widgets/category_report_widget.dart';
2025-08-06 13:05:58 +07:00
import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart';
2025-08-15 01:12:04 +07:00
import 'package:enaklo_pos/presentation/report/widgets/inventory_report_widget.dart';
2025-08-06 13:38:49 +07:00
import 'package:enaklo_pos/presentation/report/widgets/profit_loss_widget.dart';
2025-08-06 12:06:34 +07:00
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
2025-07-30 22:38:44 +07:00
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';
2025-08-06 12:32:53 +07:00
import 'package:enaklo_pos/presentation/report/widgets/product_analytic_widget.dart';
2025-07-30 22:38:44 +07:00
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> {
2025-08-06 13:10:40 +07:00
int selectedMenu = 0;
String title = 'Ringkasan Laporan Penjualan';
2025-07-30 22:38:44 +07:00
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
DateTime toDate = DateTime.now();
2025-08-01 18:27:40 +07:00
@override
void initState() {
super.initState();
2025-08-06 13:10:40 +07:00
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(fromDate, toDate),
2025-08-01 18:27:40 +07:00
);
2025-08-14 21:19:34 +07:00
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
2025-08-01 18:27:40 +07:00
}
2025-08-15 16:09:25 +07:00
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,
),
);
}
2025-08-15 17:07:29 +07:00
if (selectedMenu == 7) {
context.read<CategoryReportBloc>().add(
CategoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
}
2025-08-15 16:09:25 +07:00
}
2025-07-30 22:38:44 +07:00
@override
Widget build(BuildContext context) {
String searchDateFormatted =
2025-08-15 16:09:25 +07:00
'${fromDate.toFormattedDate2()} - ${toDate.toFormattedDate2()}';
2025-07-30 22:38:44 +07:00
return Scaffold(
2025-08-01 18:27:40 +07:00
backgroundColor: AppColors.background,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
2025-07-30 22:38:44 +07:00
children: [
2025-08-01 18:27:40 +07:00
ReportTitle(
2025-08-15 16:09:25 +07:00
searchDateFormatted: searchDateFormatted,
2025-08-01 18:27:40 +07:00
actionWidget: [
2025-08-15 16:09:25 +07:00
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,
),
2025-08-01 18:27:40 +07:00
),
),
2025-08-14 21:19:34 +07:00
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,
),
),
),
);
},
),
2025-08-01 18:27:40 +07:00
],
2025-07-30 22:38:44 +07:00
),
Expanded(
2025-08-01 18:27:40 +07:00
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: [
2025-08-06 13:10:40 +07:00
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,
),
2025-08-01 18:27:40 +07:00
ReportMenu(
label: 'Laporan Transaksi',
subtitle:
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
icon: Icons.receipt_long_outlined,
onPressed: () {
2025-08-06 12:06:34 +07:00
context.push(SalesPage(status: 'completed'));
2025-07-30 22:38:44 +07:00
},
2025-08-06 13:10:40 +07:00
isActive: selectedMenu == 1,
2025-08-01 18:27:40 +07:00
),
ReportMenu(
label: 'Laporan Penjualan Item',
subtitle:
'Laporan penjualan berdasarkan masing-masing item atau produk.',
icon: Icons.inventory_2_outlined,
onPressed: () {
2025-08-06 13:10:40 +07:00
selectedMenu = 2;
2025-08-01 18:27:40 +07:00
title = 'Laporan Penjualan Item';
setState(() {});
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
2025-08-06 12:06:34 +07:00
startDate: fromDate, endDate: toDate),
2025-08-01 18:27:40 +07:00
);
2025-07-30 22:38:44 +07:00
},
2025-08-06 13:10:40 +07:00
isActive: selectedMenu == 2,
2025-08-01 18:27:40 +07:00
),
ReportMenu(
2025-08-06 12:32:53 +07:00
label: 'Laporan Penjualan Produk',
2025-08-01 18:27:40 +07:00
subtitle:
2025-08-06 12:32:53 +07:00
'Laporan penjualan berdasarkan masing-masing produk.',
2025-08-01 18:27:40 +07:00
icon: Icons.bar_chart_outlined,
onPressed: () {
2025-08-06 13:10:40 +07:00
selectedMenu = 3;
2025-08-06 12:32:53 +07:00
title = 'Laporan Penjualan Produk';
2025-08-01 18:27:40 +07:00
setState(() {});
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
2025-08-06 12:32:53 +07:00
fromDate,
toDate,
),
2025-07-30 22:38:44 +07:00
);
},
2025-08-01 18:27:40 +07:00
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(
2025-08-06 11:23:03 +07:00
startDate: fromDate,
endDate: toDate,
),
2025-08-01 18:27:40 +07:00
);
},
isActive: selectedMenu == 4,
),
2025-08-06 13:38:49 +07:00
ReportMenu(
2025-08-14 17:32:16 +07:00
label: 'Laporan Untung Rugi',
2025-08-06 13:38:49 +07:00
subtitle: 'Laporan untung rugi penjualan.',
icon: Icons.trending_down,
onPressed: () {
selectedMenu = 5;
2025-08-14 17:32:16 +07:00
title = 'Laporan Untung Rugi';
2025-08-06 13:38:49 +07:00
setState(() {});
context.read<ProfitLossBloc>().add(
ProfitLossEvent.getProfitLoss(
fromDate,
toDate,
),
);
},
isActive: selectedMenu == 5,
),
2025-08-15 01:12:04 +07:00
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,
),
2025-08-15 17:07:29 +07:00
ReportMenu(
label: 'Laporan Kategori',
subtitle: 'Laporan kategori produk',
icon: Icons.archive_outlined,
onPressed: () {
selectedMenu = 7;
title = 'Laporan Kategori';
setState(() {});
context.read<CategoryReportBloc>().add(
CategoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
},
isActive: selectedMenu == 7,
),
2025-08-01 18:27:40 +07:00
],
),
),
),
),
),
// RIGHT CONTENT
Expanded(
flex: 4,
child: selectedMenu == 0
2025-08-06 13:10:40 +07:00
? BlocBuilder<SummaryBloc, SummaryState>(
2025-08-01 18:27:40 +07:00
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
2025-08-06 13:10:40 +07:00
success: (data) {
return DashboardAnalyticWidget(
data: data,
2025-08-01 18:27:40 +07:00
title: title,
searchDateFormatted: searchDateFormatted,
);
},
);
},
)
: selectedMenu == 1
2025-08-06 13:10:40 +07:00
? BlocBuilder<TransactionReportBloc,
TransactionReportState>(
2025-08-01 18:27:40 +07:00
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
2025-08-06 13:10:40 +07:00
loaded: (transactionReport) {
return TransactionReportWidget(
transactionReport: transactionReport,
2025-08-01 18:27:40 +07:00
title: title,
searchDateFormatted:
searchDateFormatted,
headerWidgets:
2025-08-06 13:10:40 +07:00
_getTitleReportPageWidget(),
2025-08-01 18:27:40 +07:00
);
},
);
},
)
: selectedMenu == 2
2025-08-06 13:10:40 +07:00
? BlocBuilder<ItemSalesReportBloc,
ItemSalesReportState>(
2025-08-01 18:27:40 +07:00
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
2025-08-06 13:10:40 +07:00
loaded: (itemSales) {
return ItemSalesReportWidget(
sales: itemSales,
2025-08-01 18:27:40 +07:00
title: title,
searchDateFormatted:
searchDateFormatted,
2025-08-06 13:10:40 +07:00
headerWidgets:
_getItemSalesPageWidget(),
2025-08-01 18:27:40 +07:00
);
},
);
},
)
: selectedMenu == 3
2025-08-06 13:10:40 +07:00
? BlocBuilder<ProductSalesBloc,
ProductSalesState>(
2025-08-01 18:27:40 +07:00
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
2025-08-06 13:10:40 +07:00
success: (products) {
return ProductAnalyticsWidget(
2025-08-01 18:27:40 +07:00
title: title,
searchDateFormatted:
searchDateFormatted,
2025-08-06 13:10:40 +07:00
productData: products,
2025-08-01 18:27:40 +07:00
);
},
);
},
)
: 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(),
);
},
);
},
)
2025-08-06 13:38:49 +07:00
: 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,
);
},
);
},
)
2025-08-15 01:12:04 +07:00
: 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,
);
},
);
},
)
2025-08-15 17:07:29 +07:00
: selectedMenu == 7
? BlocBuilder<
CategoryReportBloc,
CategoryReportState>(
builder:
(context, state) {
return state
.maybeWhen(
orElse: () =>
const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(
message);
},
loaded: (data) {
return CategoryReportWidget(
title: title,
searchDateFormatted:
searchDateFormatted,
categoryAnalyticData:
data,
);
},
);
},
)
: const SizedBox.shrink()),
2025-08-01 18:27:40 +07:00
],
),
),
2025-07-30 22:38:44 +07:00
],
),
);
}
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),
2025-08-01 18:27:40 +07:00
_getTitleItemWidget('Order', 100),
_getTitleItemWidget('Product', 200),
2025-07-30 22:38:44 +07:00
_getTitleItemWidget('Qty', 60),
2025-08-01 18:27:40 +07:00
_getTitleItemWidget('Price', 150),
_getTitleItemWidget('Total Price', 160),
2025-07-30 22:38:44 +07:00
];
}
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,
),
),
),
);
}
}