diff --git a/lib/data/datasources/analytic_remote_datasource.dart b/lib/data/datasources/analytic_remote_datasource.dart new file mode 100644 index 0000000..113dbbf --- /dev/null +++ b/lib/data/datasources/analytic_remote_datasource.dart @@ -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> 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'); + } + } +} diff --git a/lib/data/models/response/payment_method_analytic_response_model.dart b/lib/data/models/response/payment_method_analytic_response_model.dart new file mode 100644 index 0000000..8cf816a --- /dev/null +++ b/lib/data/models/response/payment_method_analytic_response_model.dart @@ -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 json) => + PaymentMethodAnalyticResponseModel.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticResponseModel.fromMap(Map map) { + return PaymentMethodAnalyticResponseModel( + success: map['success'], + data: PaymentMethodAnalyticData.fromMap(map['data']), + errors: map['errors'], + ); + } + + Map 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 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 json) => + PaymentMethodAnalyticData.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticData.fromMap(Map 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.from( + map['data']?.map((x) => PaymentMethodAnalyticItem.fromMap(x)) ?? [], + ), + ); + } + + Map 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 json) => + PaymentSummary.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentSummary.fromMap(Map map) { + return PaymentSummary( + totalAmount: map['total_amount'], + totalOrders: map['total_orders'], + totalPayments: map['total_payments'], + averageOrderValue: (map['average_order_value'] as num).toDouble(), + ); + } + + Map 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 json) => + PaymentMethodAnalyticItem.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticItem.fromMap(Map 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 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, + }; + } +} diff --git a/lib/main.dart b/lib/main.dart index c7b7eb8..ad3bf3c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 { create: (context) => ItemSalesReportBloc(OrderItemRemoteDatasource()), ), BlocProvider( - create: (context) => PaymentMethodReportBloc(OrderRemoteDatasource()), + create: (context) => + PaymentMethodReportBloc(AnalyticRemoteDatasource()), ), BlocProvider( create: (context) => DaySalesBloc(ProductLocalDatasource.instance), diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart index a45ed06..ddf6ff7 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart @@ -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 { - 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, + ), + ), + ); }); } -} \ No newline at end of file +} diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart index 2671d58..7de5a89 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart @@ -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({ - required TResult Function(String startDate, String endDate) + required TResult Function(DateTime startDate, DateTime endDate) getPaymentMethodReport, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(String startDate, String endDate)? getPaymentMethodReport, + TResult? Function(DateTime startDate, DateTime endDate)? + getPaymentMethodReport, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - 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({ - 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? 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 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({ 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? 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 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({ 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? 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 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({ 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? 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 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({ 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? 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 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({ 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? 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 Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart index 55cd10f..81888b8 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart @@ -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; -} \ No newline at end of file +} diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart index 70cb241..5e85d75 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart @@ -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; -} \ No newline at end of file +} diff --git a/lib/presentation/report/pages/report_page.dart b/lib/presentation/report/pages/report_page.dart index 3fb1c9c..a1dd47f 100644 --- a/lib/presentation/report/pages/report_page.dart +++ b/lib/presentation/report/pages/report_page.dart @@ -202,12 +202,9 @@ class _ReportPageState extends State { context.read().add( PaymentMethodReportEvent .getPaymentMethodReport( - startDate: - DateFormatter.formatDateTime( - fromDate), - endDate: - DateFormatter.formatDateTime( - toDate)), + startDate: fromDate, + endDate: toDate, + ), ); }, isActive: selectedMenu == 4, diff --git a/lib/presentation/report/widgets/payment_method_report_widget.dart b/lib/presentation/report/widgets/payment_method_report_widget.dart index 00e1659..09e2e14 100644 --- a/lib/presentation/report/widgets/payment_method_report_widget.dart +++ b/lib/presentation/report/widgets/payment_method_report_widget.dart @@ -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 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, ), ), );