feat: payment method report

This commit is contained in:
efrilm 2025-08-06 11:23:03 +07:00
parent ae4f7b06ce
commit fd254c22fd
9 changed files with 608 additions and 164 deletions

View File

@ -0,0 +1,47 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/constants/variables.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/models/response/payment_method_analytic_response_model.dart';
import 'package:intl/intl.dart';
class AnalyticRemoteDatasource {
final Dio dio = DioClient.instance;
Future<Either<String, PaymentMethodAnalyticResponseModel>> getPaymentMethod({
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/payment-methods',
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(PaymentMethodAnalyticResponseModel.fromMap(response.data));
} else {
return left(response.data.toString());
}
} 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,170 @@
class PaymentMethodAnalyticResponseModel {
final bool success;
final PaymentMethodAnalyticData data;
final dynamic errors;
PaymentMethodAnalyticResponseModel({
required this.success,
required this.data,
this.errors,
});
factory PaymentMethodAnalyticResponseModel.fromJson(
Map<String, dynamic> json) =>
PaymentMethodAnalyticResponseModel.fromMap(json);
Map<String, dynamic> toJson() => toMap();
factory PaymentMethodAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return PaymentMethodAnalyticResponseModel(
success: map['success'],
data: PaymentMethodAnalyticData.fromMap(map['data']),
errors: map['errors'],
);
}
Map<String, dynamic> toMap() {
return {
'success': success,
'data': data.toMap(),
'errors': errors,
};
}
}
class PaymentMethodAnalyticData {
final String organizationId;
final String outletId;
final DateTime dateFrom;
final DateTime dateTo;
final String groupBy;
final PaymentSummary summary;
final List<PaymentMethodAnalyticItem> data;
PaymentMethodAnalyticData({
required this.organizationId,
required this.outletId,
required this.dateFrom,
required this.dateTo,
required this.groupBy,
required this.summary,
required this.data,
});
factory PaymentMethodAnalyticData.fromJson(Map<String, dynamic> json) =>
PaymentMethodAnalyticData.fromMap(json);
Map<String, dynamic> toJson() => toMap();
factory PaymentMethodAnalyticData.fromMap(Map<String, dynamic> map) {
return PaymentMethodAnalyticData(
organizationId: map['organization_id'],
outletId: map['outlet_id'],
dateFrom: DateTime.parse(map['date_from']),
dateTo: DateTime.parse(map['date_to']),
groupBy: map['group_by'],
summary: PaymentSummary.fromMap(map['summary']),
data: List<PaymentMethodAnalyticItem>.from(
map['data']?.map((x) => PaymentMethodAnalyticItem.fromMap(x)) ?? [],
),
);
}
Map<String, dynamic> toMap() {
return {
'organization_id': organizationId,
'outlet_id': outletId,
'date_from': dateFrom.toIso8601String(),
'date_to': dateTo.toIso8601String(),
'group_by': groupBy,
'summary': summary.toMap(),
'data': data.map((x) => x.toMap()).toList(),
};
}
}
class PaymentSummary {
final int totalAmount;
final int totalOrders;
final int totalPayments;
final double averageOrderValue;
PaymentSummary({
required this.totalAmount,
required this.totalOrders,
required this.totalPayments,
required this.averageOrderValue,
});
factory PaymentSummary.fromJson(Map<String, dynamic> json) =>
PaymentSummary.fromMap(json);
Map<String, dynamic> toJson() => toMap();
factory PaymentSummary.fromMap(Map<String, dynamic> map) {
return PaymentSummary(
totalAmount: map['total_amount'],
totalOrders: map['total_orders'],
totalPayments: map['total_payments'],
averageOrderValue: (map['average_order_value'] as num).toDouble(),
);
}
Map<String, dynamic> toMap() {
return {
'total_amount': totalAmount,
'total_orders': totalOrders,
'total_payments': totalPayments,
'average_order_value': averageOrderValue,
};
}
}
class PaymentMethodAnalyticItem {
final String paymentMethodId;
final String paymentMethodName;
final String paymentMethodType;
final int totalAmount;
final int orderCount;
final int paymentCount;
final int percentage;
PaymentMethodAnalyticItem({
required this.paymentMethodId,
required this.paymentMethodName,
required this.paymentMethodType,
required this.totalAmount,
required this.orderCount,
required this.paymentCount,
required this.percentage,
});
factory PaymentMethodAnalyticItem.fromJson(Map<String, dynamic> json) =>
PaymentMethodAnalyticItem.fromMap(json);
Map<String, dynamic> toJson() => toMap();
factory PaymentMethodAnalyticItem.fromMap(Map<String, dynamic> map) {
return PaymentMethodAnalyticItem(
paymentMethodId: map['payment_method_id'],
paymentMethodName: map['payment_method_name'],
paymentMethodType: map['payment_method_type'],
totalAmount: map['total_amount'],
orderCount: map['order_count'],
paymentCount: map['payment_count'],
percentage: map['percentage'],
);
}
Map<String, dynamic> toMap() {
return {
'payment_method_id': paymentMethodId,
'payment_method_name': paymentMethodName,
'payment_method_type': paymentMethodType,
'total_amount': totalAmount,
'order_count': orderCount,
'payment_count': paymentCount,
'percentage': percentage,
};
}
}

View File

@ -1,5 +1,6 @@
import 'dart:developer';
import 'package:enaklo_pos/core/constants/theme.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/file_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart';
@ -198,7 +199,8 @@ class _MyAppState extends State<MyApp> {
create: (context) => ItemSalesReportBloc(OrderItemRemoteDatasource()),
),
BlocProvider(
create: (context) => PaymentMethodReportBloc(OrderRemoteDatasource()),
create: (context) =>
PaymentMethodReportBloc(AnalyticRemoteDatasource()),
),
BlocProvider(
create: (context) => DaySalesBloc(ProductLocalDatasource.instance),

View File

@ -1,6 +1,6 @@
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'payment_method_report_event.dart';
@ -9,16 +9,23 @@ part 'payment_method_report_bloc.freezed.dart';
class PaymentMethodReportBloc
extends Bloc<PaymentMethodReportEvent, PaymentMethodReportState> {
final OrderRemoteDatasource datasource;
final AnalyticRemoteDatasource datasource;
PaymentMethodReportBloc(this.datasource) : super(const _Initial()) {
on<_GetPaymentMethodReport>((event, emit) async {
emit(const _Loading());
final result = await datasource.getPaymentMethodByRangeDate(
event.startDate,
event.endDate,
final result = await datasource.getPaymentMethod(
dateFrom: event.startDate,
dateTo: event.endDate,
);
result.fold((l) => emit(_Error(l)), (r) => emit(_Loaded(r.data!)));
result.fold(
(l) => emit(_Error(l)),
(r) => emit(
_Loaded(
r.data,
),
),
);
});
}
}
}

View File

@ -16,22 +16,24 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$PaymentMethodReportEvent {
String get startDate => throw _privateConstructorUsedError;
String get endDate => throw _privateConstructorUsedError;
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String startDate, String endDate)
required TResult Function(DateTime startDate, DateTime endDate)
getPaymentMethodReport,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String startDate, String endDate)? getPaymentMethodReport,
TResult? Function(DateTime startDate, DateTime endDate)?
getPaymentMethodReport,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String startDate, String endDate)? getPaymentMethodReport,
TResult Function(DateTime startDate, DateTime endDate)?
getPaymentMethodReport,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@ -66,7 +68,7 @@ abstract class $PaymentMethodReportEventCopyWith<$Res> {
$Res Function(PaymentMethodReportEvent) then) =
_$PaymentMethodReportEventCopyWithImpl<$Res, PaymentMethodReportEvent>;
@useResult
$Res call({String startDate, String endDate});
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
@ -92,11 +94,11 @@ class _$PaymentMethodReportEventCopyWithImpl<$Res,
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as String,
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as String,
as DateTime,
) as $Val);
}
}
@ -110,7 +112,7 @@ abstract class _$$GetPaymentMethodReportImplCopyWith<$Res>
__$$GetPaymentMethodReportImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String startDate, String endDate});
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
@ -135,11 +137,11 @@ class __$$GetPaymentMethodReportImplCopyWithImpl<$Res>
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as String,
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as String,
as DateTime,
));
}
}
@ -151,9 +153,9 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport {
{required this.startDate, required this.endDate});
@override
final String startDate;
final DateTime startDate;
@override
final String endDate;
final DateTime endDate;
@override
String toString() {
@ -185,7 +187,7 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String startDate, String endDate)
required TResult Function(DateTime startDate, DateTime endDate)
getPaymentMethodReport,
}) {
return getPaymentMethodReport(startDate, endDate);
@ -194,7 +196,8 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String startDate, String endDate)? getPaymentMethodReport,
TResult? Function(DateTime startDate, DateTime endDate)?
getPaymentMethodReport,
}) {
return getPaymentMethodReport?.call(startDate, endDate);
}
@ -202,7 +205,8 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport {
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String startDate, String endDate)? getPaymentMethodReport,
TResult Function(DateTime startDate, DateTime endDate)?
getPaymentMethodReport,
required TResult orElse(),
}) {
if (getPaymentMethodReport != null) {
@ -243,13 +247,13 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport {
abstract class _GetPaymentMethodReport implements PaymentMethodReportEvent {
const factory _GetPaymentMethodReport(
{required final String startDate,
required final String endDate}) = _$GetPaymentMethodReportImpl;
{required final DateTime startDate,
required final DateTime endDate}) = _$GetPaymentMethodReportImpl;
@override
String get startDate;
DateTime get startDate;
@override
String get endDate;
DateTime get endDate;
/// Create a copy of PaymentMethodReportEvent
/// with the given fields replaced by the non-null parameter values.
@ -265,7 +269,7 @@ mixin _$PaymentMethodReportState {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentMethodData data) loaded,
required TResult Function(PaymentMethodAnalyticData data) loaded,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@ -273,7 +277,7 @@ mixin _$PaymentMethodReportState {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentMethodData data)? loaded,
TResult? Function(PaymentMethodAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@ -281,7 +285,7 @@ mixin _$PaymentMethodReportState {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentMethodData data)? loaded,
TResult Function(PaymentMethodAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
@ -378,7 +382,7 @@ class _$InitialImpl implements _Initial {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentMethodData data) loaded,
required TResult Function(PaymentMethodAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return initial();
@ -389,7 +393,7 @@ class _$InitialImpl implements _Initial {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentMethodData data)? loaded,
TResult? Function(PaymentMethodAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return initial?.call();
@ -400,7 +404,7 @@ class _$InitialImpl implements _Initial {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentMethodData data)? loaded,
TResult Function(PaymentMethodAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
@ -495,7 +499,7 @@ class _$LoadingImpl implements _Loading {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentMethodData data) loaded,
required TResult Function(PaymentMethodAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loading();
@ -506,7 +510,7 @@ class _$LoadingImpl implements _Loading {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentMethodData data)? loaded,
TResult? Function(PaymentMethodAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loading?.call();
@ -517,7 +521,7 @@ class _$LoadingImpl implements _Loading {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentMethodData data)? loaded,
TResult Function(PaymentMethodAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
@ -575,7 +579,7 @@ abstract class _$$LoadedImplCopyWith<$Res> {
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call({PaymentMethodData data});
$Res call({PaymentMethodAnalyticData data});
}
/// @nodoc
@ -597,7 +601,7 @@ class __$$LoadedImplCopyWithImpl<$Res>
null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as PaymentMethodData,
as PaymentMethodAnalyticData,
));
}
}
@ -608,7 +612,7 @@ class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(this.data);
@override
final PaymentMethodData data;
final PaymentMethodAnalyticData data;
@override
String toString() {
@ -639,7 +643,7 @@ class _$LoadedImpl implements _Loaded {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentMethodData data) loaded,
required TResult Function(PaymentMethodAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loaded(data);
@ -650,7 +654,7 @@ class _$LoadedImpl implements _Loaded {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentMethodData data)? loaded,
TResult? Function(PaymentMethodAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(data);
@ -661,7 +665,7 @@ class _$LoadedImpl implements _Loaded {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentMethodData data)? loaded,
TResult Function(PaymentMethodAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
@ -710,9 +714,9 @@ class _$LoadedImpl implements _Loaded {
}
abstract class _Loaded implements PaymentMethodReportState {
const factory _Loaded(final PaymentMethodData data) = _$LoadedImpl;
const factory _Loaded(final PaymentMethodAnalyticData data) = _$LoadedImpl;
PaymentMethodData get data;
PaymentMethodAnalyticData get data;
/// Create a copy of PaymentMethodReportState
/// with the given fields replaced by the non-null parameter values.
@ -791,7 +795,7 @@ class _$ErrorImpl implements _Error {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentMethodData data) loaded,
required TResult Function(PaymentMethodAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return error(message);
@ -802,7 +806,7 @@ class _$ErrorImpl implements _Error {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentMethodData data)? loaded,
TResult? Function(PaymentMethodAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return error?.call(message);
@ -813,7 +817,7 @@ class _$ErrorImpl implements _Error {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentMethodData data)? loaded,
TResult Function(PaymentMethodAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {

View File

@ -3,7 +3,7 @@ part of 'payment_method_report_bloc.dart';
@freezed
class PaymentMethodReportEvent with _$PaymentMethodReportEvent {
const factory PaymentMethodReportEvent.getPaymentMethodReport({
required String startDate,
required String endDate,
required DateTime startDate,
required DateTime endDate,
}) = _GetPaymentMethodReport;
}
}

View File

@ -4,6 +4,7 @@ part of 'payment_method_report_bloc.dart';
class PaymentMethodReportState with _$PaymentMethodReportState {
const factory PaymentMethodReportState.initial() = _Initial;
const factory PaymentMethodReportState.loading() = _Loading;
const factory PaymentMethodReportState.loaded(PaymentMethodData data) = _Loaded;
const factory PaymentMethodReportState.loaded(
PaymentMethodAnalyticData data) = _Loaded;
const factory PaymentMethodReportState.error(String message) = _Error;
}
}

View File

@ -202,12 +202,9 @@ class _ReportPageState extends State<ReportPage> {
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent
.getPaymentMethodReport(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate:
DateFormatter.formatDateTime(
toDate)),
startDate: fromDate,
endDate: toDate,
),
);
},
isActive: selectedMenu == 4,

View File

@ -1,12 +1,12 @@
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart';
import '../../../core/components/spaces.dart';
class PaymentMethodReportWidget extends StatelessWidget {
final PaymentMethodData paymentMethodData;
final PaymentMethodAnalyticData paymentMethodData;
final String title;
final String searchDateFormatted;
final List<Widget> headerWidgets;
@ -23,127 +23,343 @@ class PaymentMethodReportWidget extends StatelessWidget {
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,
),
),
body: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
left: BorderSide(
color: Colors.grey.shade200,
width: 1,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Section
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.blue.shade200,
width: 1,
),
),
child: Text(
searchDateFormatted,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blue.shade700,
),
),
),
],
),
const SpaceHeight(24), // Summary Cards
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.green.shade200,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pendapatan Total',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.green.shade700,
),
),
const SizedBox(height: 8),
Text(
paymentMethodData
.summary.totalAmount.currencyFormatRpV2,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.green.shade800,
),
),
],
),
),
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.orange.shade200,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Jumlah Pesanan',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.orange.shade700,
),
),
const SizedBox(height: 8),
Text(
paymentMethodData.summary.totalOrders.toString(),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.orange.shade800,
),
),
],
),
),
),
],
),
const SpaceHeight(16),
// Average Order Value Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.purple.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.purple.shade200,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
'Nilai Pesanan Rata-rata',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.purple.shade700,
),
),
const SpaceHeight(8.0),
const SizedBox(height: 8),
Text(
searchDateFormatted,
style: const TextStyle(
fontSize: 16,
color: AppColors.grey,
paymentMethodData.summary.averageOrderValue
.round()
.currencyFormatRpV2,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.purple.shade800,
),
),
],
),
Row(
),
const SpaceHeight(24),
// Payment Methods Section
Text(
'Rincian Metode Pembayaran',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
const SpaceHeight(16),
// Payment Method Item
...List.generate(paymentMethodData.data.length, (index) {
final item = paymentMethodData.data[index];
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
),
child: Row(
children: [
// Payment Method Icon
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.primary,
width: 1,
),
),
child: Icon(
Icons.money,
color: AppColors.white,
size: 20,
),
),
const SizedBox(width: 16),
// Payment Method Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
item.paymentMethodName,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
Text(
"${item.percentage}%",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
item.totalAmount.currencyFormatRpV2,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey.shade600,
),
),
Text(
'${item.orderCount} Pesanan',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey.shade600,
),
),
],
),
const SizedBox(height: 8),
// Progress Bar
Container(
width: double.infinity,
height: 6,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(3),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor:
(item.percentage / 100), // 100%
child: Container(
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius:
BorderRadius.circular(3),
),
),
),
),
],
),
),
],
),
),
],
),
);
}),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.blue.shade200,
width: 1,
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(8.0),
),
Icon(
Icons.info_outline,
color: Colors.blue.shade600,
size: 16,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Total: ${paymentMethodData.total?.currencyFormatRpV2 ?? 'Rp 0'}',
style: const TextStyle(
color: AppColors.white,
fontWeight: FontWeight.w600,
'All payments processed successfully with 100% cash transactions',
style: TextStyle(
fontSize: 12,
color: Colors.blue.shade700,
),
),
),
],
),
],
),
),
// 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,
),
),
);