From b3351026772b73c935ec9b5ddb02de0185c357da Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 30 Oct 2025 16:35:47 +0700 Subject: [PATCH] payment --- .../checkout_form_bloc.freezed.dart | 33 + .../order/order_loader/order_loader_bloc.dart | 1 + .../order_loader_bloc.freezed.dart | 25 +- .../order_loader/order_loader_state.dart | 1 + .../payment_form/payment_form_bloc.dart | 69 ++ .../payment_form_bloc.freezed.dart | 795 ++++++++++++++++++ .../payment_form/payment_form_event.dart | 9 + .../payment_form/payment_form_state.dart | 18 + .../repositories/i_order_repository.dart | 4 + .../order/dtos/payment_request_dto.dart | 20 + .../order/repositories/order_repository.dart | 21 + lib/injection.config.dart | 5 + .../pages/order/widgets/order_left_panel.dart | 1 + .../order/widgets/order_right_panel.dart | 8 +- .../pages/order/widgets/order_title.dart | 4 +- .../pages/payment/payment_page.dart | 103 +++ .../payment/widgets/payment_left_panel.dart | 131 +++ .../payment/widgets/payment_right_panel.dart | 226 +++++ lib/presentation/router/app_router.dart | 3 + lib/presentation/router/app_router.gr.dart | 193 +++-- 20 files changed, 1591 insertions(+), 79 deletions(-) create mode 100644 lib/application/payment/payment_form/payment_form_bloc.dart create mode 100644 lib/application/payment/payment_form/payment_form_bloc.freezed.dart create mode 100644 lib/application/payment/payment_form/payment_form_event.dart create mode 100644 lib/application/payment/payment_form/payment_form_state.dart create mode 100644 lib/presentation/pages/payment/payment_page.dart create mode 100644 lib/presentation/pages/payment/widgets/payment_left_panel.dart create mode 100644 lib/presentation/pages/payment/widgets/payment_right_panel.dart diff --git a/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart b/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart index c5949bb..0ab4d21 100644 --- a/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart +++ b/lib/application/checkout/checkout_form/checkout_form_bloc.freezed.dart @@ -1500,6 +1500,8 @@ abstract class _$$OrderAddedItemsImplCopyWith<$Res> { ) = __$$OrderAddedItemsImplCopyWithImpl<$Res>; @useResult $Res call({Order? order}); + + $OrderCopyWith<$Res>? get order; } /// @nodoc @@ -1525,6 +1527,20 @@ class __$$OrderAddedItemsImplCopyWithImpl<$Res> ), ); } + + /// Create a copy of CheckoutFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OrderCopyWith<$Res>? get order { + if (_value.order == null) { + return null; + } + + return $OrderCopyWith<$Res>(_value.order!, (value) { + return _then(_value.copyWith(order: value)); + }); + } } /// @nodoc @@ -1720,6 +1736,7 @@ abstract class $CheckoutFormStateCopyWith<$Res> { bool isLoading, }); + $OrderCopyWith<$Res>? get orderAdded; $TableCopyWith<$Res>? get table; } @@ -1811,6 +1828,20 @@ class _$CheckoutFormStateCopyWithImpl<$Res, $Val extends CheckoutFormState> ); } + /// Create a copy of CheckoutFormState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OrderCopyWith<$Res>? get orderAdded { + if (_value.orderAdded == null) { + return null; + } + + return $OrderCopyWith<$Res>(_value.orderAdded!, (value) { + return _then(_value.copyWith(orderAdded: value) as $Val); + }); + } + /// Create a copy of CheckoutFormState /// with the given fields replaced by the non-null parameter values. @override @@ -1851,6 +1882,8 @@ abstract class _$$CheckoutFormStateImplCopyWith<$Res> bool isLoading, }); + @override + $OrderCopyWith<$Res>? get orderAdded; @override $TableCopyWith<$Res>? get table; } diff --git a/lib/application/order/order_loader/order_loader_bloc.dart b/lib/application/order/order_loader/order_loader_bloc.dart index 410a278..081a10d 100644 --- a/lib/application/order/order_loader/order_loader_bloc.dart +++ b/lib/application/order/order_loader/order_loader_bloc.dart @@ -108,6 +108,7 @@ class OrderLoaderBloc extends Bloc { failureOption: none(), page: state.page + 1, hasReachedMax: orders.orders.length < 10, + totalOrder: orders.totalCount, ); }, ); diff --git a/lib/application/order/order_loader/order_loader_bloc.freezed.dart b/lib/application/order/order_loader/order_loader_bloc.freezed.dart index 9d205a4..ed65fff 100644 --- a/lib/application/order/order_loader/order_loader_bloc.freezed.dart +++ b/lib/application/order/order_loader/order_loader_bloc.freezed.dart @@ -955,6 +955,7 @@ mixin _$OrderLoaderState { String? get search => throw _privateConstructorUsedError; DateTime get startDate => throw _privateConstructorUsedError; DateTime get endDate => throw _privateConstructorUsedError; + int get totalOrder => throw _privateConstructorUsedError; bool get isFetching => throw _privateConstructorUsedError; bool get isFetchingById => throw _privateConstructorUsedError; bool get hasReachedMax => throw _privateConstructorUsedError; @@ -983,6 +984,7 @@ abstract class $OrderLoaderStateCopyWith<$Res> { String? search, DateTime startDate, DateTime endDate, + int totalOrder, bool isFetching, bool isFetchingById, bool hasReachedMax, @@ -1016,6 +1018,7 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState> Object? search = freezed, Object? startDate = null, Object? endDate = null, + Object? totalOrder = null, Object? isFetching = null, Object? isFetchingById = null, Object? hasReachedMax = null, @@ -1055,6 +1058,10 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState> ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable as DateTime, + totalOrder: null == totalOrder + ? _value.totalOrder + : totalOrder // ignore: cast_nullable_to_non_nullable + as int, isFetching: null == isFetching ? _value.isFetching : isFetching // ignore: cast_nullable_to_non_nullable @@ -1119,6 +1126,7 @@ abstract class _$$OrderLoaderStateImplCopyWith<$Res> String? search, DateTime startDate, DateTime endDate, + int totalOrder, bool isFetching, bool isFetchingById, bool hasReachedMax, @@ -1153,6 +1161,7 @@ class __$$OrderLoaderStateImplCopyWithImpl<$Res> Object? search = freezed, Object? startDate = null, Object? endDate = null, + Object? totalOrder = null, Object? isFetching = null, Object? isFetchingById = null, Object? hasReachedMax = null, @@ -1192,6 +1201,10 @@ class __$$OrderLoaderStateImplCopyWithImpl<$Res> ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable as DateTime, + totalOrder: null == totalOrder + ? _value.totalOrder + : totalOrder // ignore: cast_nullable_to_non_nullable + as int, isFetching: null == isFetching ? _value.isFetching : isFetching // ignore: cast_nullable_to_non_nullable @@ -1225,6 +1238,7 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState { this.search, required this.startDate, required this.endDate, + this.totalOrder = 0, this.isFetching = false, this.isFetchingById = false, this.hasReachedMax = false, @@ -1255,6 +1269,9 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState { final DateTime endDate; @override @JsonKey() + final int totalOrder; + @override + @JsonKey() final bool isFetching; @override @JsonKey() @@ -1268,7 +1285,7 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState { @override String toString() { - return 'OrderLoaderState(orders: $orders, failureOption: $failureOption, failureOptionGetById: $failureOptionGetById, order: $order, selectedOrder: $selectedOrder, search: $search, startDate: $startDate, endDate: $endDate, isFetching: $isFetching, isFetchingById: $isFetchingById, hasReachedMax: $hasReachedMax, page: $page)'; + return 'OrderLoaderState(orders: $orders, failureOption: $failureOption, failureOptionGetById: $failureOptionGetById, order: $order, selectedOrder: $selectedOrder, search: $search, startDate: $startDate, endDate: $endDate, totalOrder: $totalOrder, isFetching: $isFetching, isFetchingById: $isFetchingById, hasReachedMax: $hasReachedMax, page: $page)'; } @override @@ -1288,6 +1305,8 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState { (identical(other.startDate, startDate) || other.startDate == startDate) && (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.totalOrder, totalOrder) || + other.totalOrder == totalOrder) && (identical(other.isFetching, isFetching) || other.isFetching == isFetching) && (identical(other.isFetchingById, isFetchingById) || @@ -1308,6 +1327,7 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState { search, startDate, endDate, + totalOrder, isFetching, isFetchingById, hasReachedMax, @@ -1336,6 +1356,7 @@ abstract class _OrderLoaderState implements OrderLoaderState { final String? search, required final DateTime startDate, required final DateTime endDate, + final int totalOrder, final bool isFetching, final bool isFetchingById, final bool hasReachedMax, @@ -1359,6 +1380,8 @@ abstract class _OrderLoaderState implements OrderLoaderState { @override DateTime get endDate; @override + int get totalOrder; + @override bool get isFetching; @override bool get isFetchingById; diff --git a/lib/application/order/order_loader/order_loader_state.dart b/lib/application/order/order_loader/order_loader_state.dart index 3bb0a05..776b0ef 100644 --- a/lib/application/order/order_loader/order_loader_state.dart +++ b/lib/application/order/order_loader/order_loader_state.dart @@ -11,6 +11,7 @@ class OrderLoaderState with _$OrderLoaderState { String? search, required DateTime startDate, required DateTime endDate, + @Default(0) int totalOrder, @Default(false) bool isFetching, @Default(false) bool isFetchingById, @Default(false) bool hasReachedMax, diff --git a/lib/application/payment/payment_form/payment_form_bloc.dart b/lib/application/payment/payment_form/payment_form_bloc.dart new file mode 100644 index 0000000..027dd7b --- /dev/null +++ b/lib/application/payment/payment_form/payment_form_bloc.dart @@ -0,0 +1,69 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart' hide Order; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart' hide Order; + +import '../../../domain/order/order.dart'; +import '../../../domain/payment_method/payment_method.dart'; + +part 'payment_form_event.dart'; +part 'payment_form_state.dart'; +part 'payment_form_bloc.freezed.dart'; + +@injectable +class PaymentFormBloc extends Bloc { + final IOrderRepository _repository; + PaymentFormBloc(this._repository) : super(PaymentFormState.initial()) { + on(_onPaymentFormEvent); + } + + Future _onPaymentFormEvent( + PaymentFormEvent event, + Emitter emit, + ) { + return event.map( + setOrder: (e) async { + List pendingItems = e.order.orderItems + .where((item) => item.status == 'pending') + .toList(); + + emit(state.copyWith(order: e.order, pendingItems: pendingItems)); + }, + setPaymentMethod: (e) async { + emit(state.copyWith(paymentMethod: e.paymentMethod)); + }, + submitted: (e) async { + Either failureOrPayment; + + emit(state.copyWith(isSubmitting: true, failureOrPayment: none())); + + final request = PaymentRequest( + orderId: state.order.id, + paymentMethodId: state.paymentMethod?.id ?? '', + amount: state.order.totalAmount, + transactionId: '', + splitNumber: 1, + splitTotal: 1, + splitDescription: '', + paymentOrderItems: state.pendingItems + .map( + (item) => PaymentItemRequest( + orderItemId: item.id, + amount: item.totalPrice, + ), + ) + .toList(), + ); + + failureOrPayment = await _repository.createPayment(request: request); + + emit( + state.copyWith( + isSubmitting: false, + failureOrPayment: optionOf(failureOrPayment), + ), + ); + }, + ); + } +} diff --git a/lib/application/payment/payment_form/payment_form_bloc.freezed.dart b/lib/application/payment/payment_form/payment_form_bloc.freezed.dart new file mode 100644 index 0000000..b78200f --- /dev/null +++ b/lib/application/payment/payment_form/payment_form_bloc.freezed.dart @@ -0,0 +1,795 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'payment_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$PaymentFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) setOrder, + required TResult Function(PaymentMethod paymentMethod) setPaymentMethod, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? setOrder, + TResult? Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? setOrder, + TResult Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_SetOrder value) setOrder, + required TResult Function(_SetPayment value) setPaymentMethod, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SetOrder value)? setOrder, + TResult? Function(_SetPayment value)? setPaymentMethod, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SetOrder value)? setOrder, + TResult Function(_SetPayment value)? setPaymentMethod, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PaymentFormEventCopyWith<$Res> { + factory $PaymentFormEventCopyWith( + PaymentFormEvent value, + $Res Function(PaymentFormEvent) then, + ) = _$PaymentFormEventCopyWithImpl<$Res, PaymentFormEvent>; +} + +/// @nodoc +class _$PaymentFormEventCopyWithImpl<$Res, $Val extends PaymentFormEvent> + implements $PaymentFormEventCopyWith<$Res> { + _$PaymentFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$SetOrderImplCopyWith<$Res> { + factory _$$SetOrderImplCopyWith( + _$SetOrderImpl value, + $Res Function(_$SetOrderImpl) then, + ) = __$$SetOrderImplCopyWithImpl<$Res>; + @useResult + $Res call({Order order}); + + $OrderCopyWith<$Res> get order; +} + +/// @nodoc +class __$$SetOrderImplCopyWithImpl<$Res> + extends _$PaymentFormEventCopyWithImpl<$Res, _$SetOrderImpl> + implements _$$SetOrderImplCopyWith<$Res> { + __$$SetOrderImplCopyWithImpl( + _$SetOrderImpl _value, + $Res Function(_$SetOrderImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? order = null}) { + return _then( + _$SetOrderImpl( + null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + ), + ); + } + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OrderCopyWith<$Res> get order { + return $OrderCopyWith<$Res>(_value.order, (value) { + return _then(_value.copyWith(order: value)); + }); + } +} + +/// @nodoc + +class _$SetOrderImpl implements _SetOrder { + const _$SetOrderImpl(this.order); + + @override + final Order order; + + @override + String toString() { + return 'PaymentFormEvent.setOrder(order: $order)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SetOrderImpl && + (identical(other.order, order) || other.order == order)); + } + + @override + int get hashCode => Object.hash(runtimeType, order); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SetOrderImplCopyWith<_$SetOrderImpl> get copyWith => + __$$SetOrderImplCopyWithImpl<_$SetOrderImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) setOrder, + required TResult Function(PaymentMethod paymentMethod) setPaymentMethod, + required TResult Function() submitted, + }) { + return setOrder(order); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? setOrder, + TResult? Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult? Function()? submitted, + }) { + return setOrder?.call(order); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? setOrder, + TResult Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (setOrder != null) { + return setOrder(order); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SetOrder value) setOrder, + required TResult Function(_SetPayment value) setPaymentMethod, + required TResult Function(_Submitted value) submitted, + }) { + return setOrder(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SetOrder value)? setOrder, + TResult? Function(_SetPayment value)? setPaymentMethod, + TResult? Function(_Submitted value)? submitted, + }) { + return setOrder?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SetOrder value)? setOrder, + TResult Function(_SetPayment value)? setPaymentMethod, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (setOrder != null) { + return setOrder(this); + } + return orElse(); + } +} + +abstract class _SetOrder implements PaymentFormEvent { + const factory _SetOrder(final Order order) = _$SetOrderImpl; + + Order get order; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SetOrderImplCopyWith<_$SetOrderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SetPaymentImplCopyWith<$Res> { + factory _$$SetPaymentImplCopyWith( + _$SetPaymentImpl value, + $Res Function(_$SetPaymentImpl) then, + ) = __$$SetPaymentImplCopyWithImpl<$Res>; + @useResult + $Res call({PaymentMethod paymentMethod}); + + $PaymentMethodCopyWith<$Res> get paymentMethod; +} + +/// @nodoc +class __$$SetPaymentImplCopyWithImpl<$Res> + extends _$PaymentFormEventCopyWithImpl<$Res, _$SetPaymentImpl> + implements _$$SetPaymentImplCopyWith<$Res> { + __$$SetPaymentImplCopyWithImpl( + _$SetPaymentImpl _value, + $Res Function(_$SetPaymentImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? paymentMethod = null}) { + return _then( + _$SetPaymentImpl( + null == paymentMethod + ? _value.paymentMethod + : paymentMethod // ignore: cast_nullable_to_non_nullable + as PaymentMethod, + ), + ); + } + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PaymentMethodCopyWith<$Res> get paymentMethod { + return $PaymentMethodCopyWith<$Res>(_value.paymentMethod, (value) { + return _then(_value.copyWith(paymentMethod: value)); + }); + } +} + +/// @nodoc + +class _$SetPaymentImpl implements _SetPayment { + const _$SetPaymentImpl(this.paymentMethod); + + @override + final PaymentMethod paymentMethod; + + @override + String toString() { + return 'PaymentFormEvent.setPaymentMethod(paymentMethod: $paymentMethod)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SetPaymentImpl && + (identical(other.paymentMethod, paymentMethod) || + other.paymentMethod == paymentMethod)); + } + + @override + int get hashCode => Object.hash(runtimeType, paymentMethod); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SetPaymentImplCopyWith<_$SetPaymentImpl> get copyWith => + __$$SetPaymentImplCopyWithImpl<_$SetPaymentImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) setOrder, + required TResult Function(PaymentMethod paymentMethod) setPaymentMethod, + required TResult Function() submitted, + }) { + return setPaymentMethod(paymentMethod); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? setOrder, + TResult? Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult? Function()? submitted, + }) { + return setPaymentMethod?.call(paymentMethod); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? setOrder, + TResult Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (setPaymentMethod != null) { + return setPaymentMethod(paymentMethod); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SetOrder value) setOrder, + required TResult Function(_SetPayment value) setPaymentMethod, + required TResult Function(_Submitted value) submitted, + }) { + return setPaymentMethod(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SetOrder value)? setOrder, + TResult? Function(_SetPayment value)? setPaymentMethod, + TResult? Function(_Submitted value)? submitted, + }) { + return setPaymentMethod?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SetOrder value)? setOrder, + TResult Function(_SetPayment value)? setPaymentMethod, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (setPaymentMethod != null) { + return setPaymentMethod(this); + } + return orElse(); + } +} + +abstract class _SetPayment implements PaymentFormEvent { + const factory _SetPayment(final PaymentMethod paymentMethod) = + _$SetPaymentImpl; + + PaymentMethod get paymentMethod; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SetPaymentImplCopyWith<_$SetPaymentImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$PaymentFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'PaymentFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) setOrder, + required TResult Function(PaymentMethod paymentMethod) setPaymentMethod, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? setOrder, + TResult? Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? setOrder, + TResult Function(PaymentMethod paymentMethod)? setPaymentMethod, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SetOrder value) setOrder, + required TResult Function(_SetPayment value) setPaymentMethod, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SetOrder value)? setOrder, + TResult? Function(_SetPayment value)? setPaymentMethod, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SetOrder value)? setOrder, + TResult Function(_SetPayment value)? setPaymentMethod, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements PaymentFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$PaymentFormState { + Order get order => throw _privateConstructorUsedError; + List get pendingItems => throw _privateConstructorUsedError; + Option> get failureOrPayment => + throw _privateConstructorUsedError; + PaymentMethod? get paymentMethod => throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PaymentFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PaymentFormStateCopyWith<$Res> { + factory $PaymentFormStateCopyWith( + PaymentFormState value, + $Res Function(PaymentFormState) then, + ) = _$PaymentFormStateCopyWithImpl<$Res, PaymentFormState>; + @useResult + $Res call({ + Order order, + List pendingItems, + Option> failureOrPayment, + PaymentMethod? paymentMethod, + bool isSubmitting, + }); + + $OrderCopyWith<$Res> get order; + $PaymentMethodCopyWith<$Res>? get paymentMethod; +} + +/// @nodoc +class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState> + implements $PaymentFormStateCopyWith<$Res> { + _$PaymentFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + Object? pendingItems = null, + Object? failureOrPayment = null, + Object? paymentMethod = freezed, + Object? isSubmitting = null, + }) { + return _then( + _value.copyWith( + order: null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + pendingItems: null == pendingItems + ? _value.pendingItems + : pendingItems // ignore: cast_nullable_to_non_nullable + as List, + failureOrPayment: null == failureOrPayment + ? _value.failureOrPayment + : failureOrPayment // ignore: cast_nullable_to_non_nullable + as Option>, + paymentMethod: freezed == paymentMethod + ? _value.paymentMethod + : paymentMethod // ignore: cast_nullable_to_non_nullable + as PaymentMethod?, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OrderCopyWith<$Res> get order { + return $OrderCopyWith<$Res>(_value.order, (value) { + return _then(_value.copyWith(order: value) as $Val); + }); + } + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PaymentMethodCopyWith<$Res>? get paymentMethod { + if (_value.paymentMethod == null) { + return null; + } + + return $PaymentMethodCopyWith<$Res>(_value.paymentMethod!, (value) { + return _then(_value.copyWith(paymentMethod: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PaymentFormStateImplCopyWith<$Res> + implements $PaymentFormStateCopyWith<$Res> { + factory _$$PaymentFormStateImplCopyWith( + _$PaymentFormStateImpl value, + $Res Function(_$PaymentFormStateImpl) then, + ) = __$$PaymentFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + Order order, + List pendingItems, + Option> failureOrPayment, + PaymentMethod? paymentMethod, + bool isSubmitting, + }); + + @override + $OrderCopyWith<$Res> get order; + @override + $PaymentMethodCopyWith<$Res>? get paymentMethod; +} + +/// @nodoc +class __$$PaymentFormStateImplCopyWithImpl<$Res> + extends _$PaymentFormStateCopyWithImpl<$Res, _$PaymentFormStateImpl> + implements _$$PaymentFormStateImplCopyWith<$Res> { + __$$PaymentFormStateImplCopyWithImpl( + _$PaymentFormStateImpl _value, + $Res Function(_$PaymentFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + Object? pendingItems = null, + Object? failureOrPayment = null, + Object? paymentMethod = freezed, + Object? isSubmitting = null, + }) { + return _then( + _$PaymentFormStateImpl( + order: null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + pendingItems: null == pendingItems + ? _value._pendingItems + : pendingItems // ignore: cast_nullable_to_non_nullable + as List, + failureOrPayment: null == failureOrPayment + ? _value.failureOrPayment + : failureOrPayment // ignore: cast_nullable_to_non_nullable + as Option>, + paymentMethod: freezed == paymentMethod + ? _value.paymentMethod + : paymentMethod // ignore: cast_nullable_to_non_nullable + as PaymentMethod?, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$PaymentFormStateImpl implements _PaymentFormState { + _$PaymentFormStateImpl({ + required this.order, + required final List pendingItems, + required this.failureOrPayment, + this.paymentMethod, + this.isSubmitting = false, + }) : _pendingItems = pendingItems; + + @override + final Order order; + final List _pendingItems; + @override + List get pendingItems { + if (_pendingItems is EqualUnmodifiableListView) return _pendingItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_pendingItems); + } + + @override + final Option> failureOrPayment; + @override + final PaymentMethod? paymentMethod; + @override + @JsonKey() + final bool isSubmitting; + + @override + String toString() { + return 'PaymentFormState(order: $order, pendingItems: $pendingItems, failureOrPayment: $failureOrPayment, paymentMethod: $paymentMethod, isSubmitting: $isSubmitting)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PaymentFormStateImpl && + (identical(other.order, order) || other.order == order) && + const DeepCollectionEquality().equals( + other._pendingItems, + _pendingItems, + ) && + (identical(other.failureOrPayment, failureOrPayment) || + other.failureOrPayment == failureOrPayment) && + (identical(other.paymentMethod, paymentMethod) || + other.paymentMethod == paymentMethod) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + order, + const DeepCollectionEquality().hash(_pendingItems), + failureOrPayment, + paymentMethod, + isSubmitting, + ); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PaymentFormStateImplCopyWith<_$PaymentFormStateImpl> get copyWith => + __$$PaymentFormStateImplCopyWithImpl<_$PaymentFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _PaymentFormState implements PaymentFormState { + factory _PaymentFormState({ + required final Order order, + required final List pendingItems, + required final Option> failureOrPayment, + final PaymentMethod? paymentMethod, + final bool isSubmitting, + }) = _$PaymentFormStateImpl; + + @override + Order get order; + @override + List get pendingItems; + @override + Option> get failureOrPayment; + @override + PaymentMethod? get paymentMethod; + @override + bool get isSubmitting; + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PaymentFormStateImplCopyWith<_$PaymentFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/payment/payment_form/payment_form_event.dart b/lib/application/payment/payment_form/payment_form_event.dart new file mode 100644 index 0000000..d139d30 --- /dev/null +++ b/lib/application/payment/payment_form/payment_form_event.dart @@ -0,0 +1,9 @@ +part of 'payment_form_bloc.dart'; + +@freezed +class PaymentFormEvent with _$PaymentFormEvent { + const factory PaymentFormEvent.setOrder(Order order) = _SetOrder; + const factory PaymentFormEvent.setPaymentMethod(PaymentMethod paymentMethod) = + _SetPayment; + const factory PaymentFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/payment/payment_form/payment_form_state.dart b/lib/application/payment/payment_form/payment_form_state.dart new file mode 100644 index 0000000..a10c9d3 --- /dev/null +++ b/lib/application/payment/payment_form/payment_form_state.dart @@ -0,0 +1,18 @@ +part of 'payment_form_bloc.dart'; + +@freezed +class PaymentFormState with _$PaymentFormState { + factory PaymentFormState({ + required Order order, + required List pendingItems, + required Option> failureOrPayment, + PaymentMethod? paymentMethod, + @Default(false) bool isSubmitting, + }) = _PaymentFormState; + + factory PaymentFormState.initial() => PaymentFormState( + order: Order.empty(), + pendingItems: [], + failureOrPayment: none(), + ); +} diff --git a/lib/domain/order/repositories/i_order_repository.dart b/lib/domain/order/repositories/i_order_repository.dart index 06da956..8aab055 100644 --- a/lib/domain/order/repositories/i_order_repository.dart +++ b/lib/domain/order/repositories/i_order_repository.dart @@ -25,4 +25,8 @@ abstract class IOrderRepository { required String id, required List request, }); + + Future> createPayment({ + required PaymentRequest request, + }); } diff --git a/lib/infrastructure/order/dtos/payment_request_dto.dart b/lib/infrastructure/order/dtos/payment_request_dto.dart index a6cef3e..6fd953b 100644 --- a/lib/infrastructure/order/dtos/payment_request_dto.dart +++ b/lib/infrastructure/order/dtos/payment_request_dto.dart @@ -31,6 +31,20 @@ class PaymentRequestDto with _$PaymentRequestDto { paymentOrderItems: paymentOrderItems?.map((e) => e.toDomain()).toList() ?? const [], ); + + factory PaymentRequestDto.fromDomain(PaymentRequest request) => + PaymentRequestDto( + orderId: request.orderId, + paymentMethodId: request.paymentMethodId, + amount: request.amount, + transactionId: request.transactionId, + splitNumber: request.splitNumber, + splitTotal: request.splitTotal, + splitDescription: request.splitDescription, + paymentOrderItems: request.paymentOrderItems + .map((e) => PaymentItemRequestDto.fromDomain(e)) + .toList(), + ); } @freezed @@ -48,4 +62,10 @@ class PaymentItemRequestDto with _$PaymentItemRequestDto { // Optional: mapper ke domain entity PaymentItemRequest toDomain() => PaymentItemRequest(orderItemId: orderItemId ?? '', amount: amount ?? 0); + + factory PaymentItemRequestDto.fromDomain(PaymentItemRequest request) => + PaymentItemRequestDto( + orderItemId: request.orderItemId, + amount: request.amount, + ); } diff --git a/lib/infrastructure/order/repositories/order_repository.dart b/lib/infrastructure/order/repositories/order_repository.dart index f9b2ab1..2068693 100644 --- a/lib/infrastructure/order/repositories/order_repository.dart +++ b/lib/infrastructure/order/repositories/order_repository.dart @@ -130,4 +130,25 @@ class OrderRepository implements IOrderRepository { return left(const OrderFailure.unexpectedError()); } } + + @override + Future> createPayment({ + required PaymentRequest request, + }) async { + try { + final result = await _dataProvider.storePayment( + PaymentRequestDto.fromDomain(request), + ); + + if (result.hasError) { + return left(result.error!); + } + + final payment = result.data!.toDomain(); + return right(payment); + } catch (e) { + log('createPaymentError', name: _logName, error: e); + return left(const OrderFailure.unexpectedError()); + } + } } diff --git a/lib/injection.config.dart b/lib/injection.config.dart index f8c4056..552da7a 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -24,6 +24,8 @@ import 'package:apskel_pos_flutter_v2/application/order/order_loader/order_loade as _i94; import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart' as _i76; +import 'package:apskel_pos_flutter_v2/application/payment/payment_form/payment_form_bloc.dart' + as _i194; import 'package:apskel_pos_flutter_v2/application/payment_method/payment_method_loader/payment_method_loader_bloc.dart' as _i952; import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart' @@ -232,6 +234,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i94.OrderLoaderBloc>( () => _i94.OrderLoaderBloc(gh<_i299.IOrderRepository>()), ); + gh.factory<_i194.PaymentFormBloc>( + () => _i194.PaymentFormBloc(gh<_i299.IOrderRepository>()), + ); gh.factory<_i683.CustomerLoaderBloc>( () => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()), ); diff --git a/lib/presentation/pages/order/widgets/order_left_panel.dart b/lib/presentation/pages/order/widgets/order_left_panel.dart index 654e008..92c5840 100644 --- a/lib/presentation/pages/order/widgets/order_left_panel.dart +++ b/lib/presentation/pages/order/widgets/order_left_panel.dart @@ -36,6 +36,7 @@ class _OrderLeftPanelState extends State { child: Column( children: [ OrderTitle( + totalOrder: widget.state.totalOrder, startDate: widget.state.startDate, endDate: widget.state.endDate, title: widget.status == 'pending' diff --git a/lib/presentation/pages/order/widgets/order_right_panel.dart b/lib/presentation/pages/order/widgets/order_right_panel.dart index 9869831..a6ec3be 100644 --- a/lib/presentation/pages/order/widgets/order_right_panel.dart +++ b/lib/presentation/pages/order/widgets/order_right_panel.dart @@ -1,9 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../../application/order/order_loader/order_loader_bloc.dart'; import '../../../../common/theme/theme.dart'; import '../../../components/button/button.dart'; import '../../../components/spaces/space.dart'; +import '../../../router/app_router.gr.dart'; import 'order_information.dart'; import 'order_list.dart'; import 'order_list_payment.dart'; @@ -100,7 +102,11 @@ class OrderRightPanel extends StatelessWidget { SpaceWidth(8), AppElevatedButton.filled( width: 120, - onPressed: () {}, + onPressed: () { + context.router.push( + PaymentRoute(order: state.selectedOrder!), + ); + }, label: 'Bayar', icon: Icon(Icons.payment, color: Colors.white), ), diff --git a/lib/presentation/pages/order/widgets/order_title.dart b/lib/presentation/pages/order/widgets/order_title.dart index c67b35c..5a0a6ae 100644 --- a/lib/presentation/pages/order/widgets/order_title.dart +++ b/lib/presentation/pages/order/widgets/order_title.dart @@ -13,6 +13,7 @@ class OrderTitle extends StatelessWidget { final DateTime endDate; final Function(String) onChanged; final void Function(DateTime? start, DateTime? end) onDateRangeChanged; + final int totalOrder; const OrderTitle({ super.key, required this.title, @@ -20,6 +21,7 @@ class OrderTitle extends StatelessWidget { required this.endDate, required this.onChanged, required this.onDateRangeChanged, + required this.totalOrder, }); @override @@ -46,7 +48,7 @@ class OrderTitle extends StatelessWidget { style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), ), Text( - '0 Pesanan', + '$totalOrder Pesanan', style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), ), ], diff --git a/lib/presentation/pages/payment/payment_page.dart b/lib/presentation/pages/payment/payment_page.dart new file mode 100644 index 0000000..e18c487 --- /dev/null +++ b/lib/presentation/pages/payment/payment_page.dart @@ -0,0 +1,103 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../application/payment/payment_form/payment_form_bloc.dart'; +import '../../../application/payment_method/payment_method_loader/payment_method_loader_bloc.dart'; +import '../../../common/theme/theme.dart'; +import '../../../domain/order/order.dart'; +import '../../../injection.dart'; +import '../../components/spaces/space.dart'; +import '../../components/toast/flushbar.dart'; +import 'widgets/payment_left_panel.dart'; +import 'widgets/payment_right_panel.dart'; + +@RoutePage() +class PaymentPage extends StatelessWidget implements AutoRouteWrapper { + final Order order; + const PaymentPage({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => p.failureOrPayment != c.failureOrPayment, + listener: (context, state) { + state.failureOrPayment.fold( + () {}, + (either) => either.fold( + (f) => AppFlushbar.showOrderFailureToast(context, f), + (data) {}, + ), + ); + }, + child: Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar( + backgroundColor: AppColor.white, + elevation: 0, + title: const Text( + 'Pembayaran', + style: TextStyle(color: AppColor.primary), + ), + leading: IconButton( + onPressed: () => context.router.maybePop(), + icon: Icon(Icons.arrow_back, color: AppColor.primary), + ), + ), + body: LayoutBuilder( + builder: (context, constraints) { + final isWide = constraints.maxWidth > 800; + + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.all(16), + child: isWide + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: PaymentLeftPanel(state: state), + ), + const SizedBox(width: 24), + Expanded( + flex: 2, + child: PaymentRightPanel(state: state), + ), + ], + ) + : SingleChildScrollView( + child: Column( + children: [ + PaymentLeftPanel(state: state), + const SpaceHeight(24), + PaymentRightPanel(state: state), + ], + ), + ), + ); + }, + ); + }, + ), + ), + ); + } + + @override + Widget wrappedRoute(BuildContext context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + getIt()..add(PaymentFormEvent.setOrder(order)), + ), + BlocProvider( + create: (context) => + getIt() + ..add(PaymentMethodLoaderEvent.fetched(isRefresh: true)), + ), + ], + child: this, + ); +} diff --git a/lib/presentation/pages/payment/widgets/payment_left_panel.dart b/lib/presentation/pages/payment/widgets/payment_left_panel.dart new file mode 100644 index 0000000..ce712e1 --- /dev/null +++ b/lib/presentation/pages/payment/widgets/payment_left_panel.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; + +import '../../../../application/payment/payment_form/payment_form_bloc.dart'; +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../components/border/dashed_border.dart'; +import '../../../components/spaces/space.dart'; + +class PaymentLeftPanel extends StatelessWidget { + final PaymentFormState state; + const PaymentLeftPanel({super.key, required this.state}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Detail Order', + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + const SpaceHeight(16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'No. Pesanan', + style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + ), + Text( + state.order.orderNumber, + style: AppStyle.md.copyWith(fontWeight: FontWeight.bold), + ), + ], + ), + SpaceHeight(8), + Divider(color: AppColor.border), + SpaceHeight(8), + Expanded( + child: state.pendingItems.isEmpty + ? const Center(child: Text('Tidak ada item')) + : ListView.separated( + shrinkWrap: true, + itemCount: state.pendingItems.length, + separatorBuilder: (_, __) => + Divider(color: AppColor.border), + itemBuilder: (context, index) { + final item = state.pendingItems[index]; + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + item.productName, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.primary, + ), + ), + subtitle: Text( + 'Qty: ${item.quantity} | ${item.productVariantName}', + style: AppStyle.sm, + ), + trailing: Text( + (item.totalPrice).currencyFormatRpV2, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ); + }, + ), + ), + Divider(color: AppColor.border), + _buildSummaryRow('Subtotal', state.order.subtotal), + _buildSummaryRow('Pajak', state.order.taxAmount), + _buildSummaryRow('Diskon', state.order.discountAmount), + SpaceHeight(8), + DashedDivider(color: AppColor.border), + SpaceHeight(8), + _buildSummaryRow( + 'Total', + state.order.totalAmount, + isTotal: true, + color: AppColor.primary, + ), + ], + ), + ); + } + + Widget _buildSummaryRow( + String label, + int amount, { + bool isTotal = false, + Color? color, + }) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + fontSize: isTotal ? 18 : 14, + color: color ?? Colors.black, + ), + ), + Text( + amount.currencyFormatRpV2, + style: TextStyle( + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + fontSize: isTotal ? 18 : 14, + color: color ?? Colors.black, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/payment/widgets/payment_right_panel.dart b/lib/presentation/pages/payment/widgets/payment_right_panel.dart new file mode 100644 index 0000000..5c0fb12 --- /dev/null +++ b/lib/presentation/pages/payment/widgets/payment_right_panel.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/payment/payment_form/payment_form_bloc.dart'; +import '../../../../application/payment_method/payment_method_loader/payment_method_loader_bloc.dart'; +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../components/button/button.dart'; +import '../../../components/card/payment_card.dart'; +import '../../../components/error/payment_method_error_state_widget.dart'; +import '../../../components/field/field.dart'; +import '../../../components/loader/loader_with_text.dart'; +import '../../../components/spaces/space.dart'; +import '../../../components/toast/flushbar.dart'; + +class PaymentRightPanel extends StatefulWidget { + final PaymentFormState state; + const PaymentRightPanel({super.key, required this.state}); + + @override + State createState() => _PaymentRightPanelState(); +} + +class _PaymentRightPanelState extends State { + TextEditingController totalPriceController = TextEditingController(); + + int priceValue = 0; + int pasMoney1 = 0; + int pasMoney2 = 0; + int pasMoney3 = 0; + + initMoney() { + setState(() { + priceValue = widget.state.order.totalAmount; + pasMoney1 = widget.state.order.totalAmount; + pasMoney2 = pasMoney1 ~/ 50000 * 50000 + 50000; + pasMoney3 = pasMoney1 ~/ 50000 * 50000 + 100000; + totalPriceController.text = + widget.state.order.totalAmount.currencyFormatRpV2; + }); + } + + @override + void initState() { + super.initState(); + initMoney(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pembayaran', + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SpaceHeight(24), + Text( + 'Metode Pembayaran', + style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + ), + SpaceHeight(12), + BlocBuilder< + PaymentMethodLoaderBloc, + PaymentMethodLoaderState + >( + builder: (context, pmState) { + if (pmState.isFetching) { + return Center(child: LoaderWithText()); + } + return pmState.failureOption.fold( + () => Wrap( + spacing: 12.0, + runSpacing: 8.0, + children: pmState.paymentMethods.map((item) { + // Set default selected payment method if none selected or if current selection is not in the list + if (widget.state.paymentMethod == null || + !pmState.paymentMethods.any( + (method) => + method.id == + widget.state.paymentMethod?.id, + )) { + context.read().add( + PaymentFormEvent.setPaymentMethod( + pmState.paymentMethods.first, + ), + ); + } + + return PaymentCard( + payment: item, + isSelected: widget.state.paymentMethod == item, + onSelected: (_) { + context.read().add( + PaymentFormEvent.setPaymentMethod(item), + ); + }, + ); + }).toList(), + ), + (f) => PaymentMethodErrorStateWidget(failure: f), + ); + }, + ), + SpaceHeight(24), + if (widget.state.paymentMethod != null && + widget.state.paymentMethod!.type == 'cash') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Total Bayar', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(8.0), + AppTextFormField( + label: 'Total Bayar', + showLabel: false, + keyboardType: TextInputType.number, + controller: totalPriceController, + onChanged: (value) { + priceValue = value.toIntegerFromText; + final int newValue = value.toIntegerFromText; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition( + TextPosition( + offset: totalPriceController.text.length, + ), + ); + }, + ), + const SpaceHeight(20.0), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + AppElevatedButton.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = pasMoney1 + .toString() + .currencyFormatRpV2; + priceValue = pasMoney1; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + AppElevatedButton.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = pasMoney2 + .toString() + .currencyFormatRpV2; + priceValue = pasMoney2; + }, + label: pasMoney2.toString().currencyFormatRpV2, + ), + const SpaceWidth(20.0), + AppElevatedButton.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = pasMoney3 + .toString() + .currencyFormatRpV2; + priceValue = pasMoney3; + }, + label: pasMoney3.toString().currencyFormatRpV2, + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + AppElevatedButton.filled( + onPressed: () { + if (widget.state.paymentMethod == null) { + AppFlushbar.showError( + context, + 'Pilih metode pembayaran terlebih dahulu', + ); + return; + } + + if (widget.state.paymentMethod?.type == "cash" && + priceValue == 0) { + AppFlushbar.showError(context, 'Total bayar tidak boleh 0'); + return; + } + + if (!widget.state.isSubmitting) { + context.read().add( + PaymentFormEvent.submitted(), + ); + } + }, + label: 'Bayar', + isLoading: widget.state.isSubmitting, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 1450a2e..d1930d5 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -33,5 +33,8 @@ class AppRouter extends RootStackRouter { AutoRoute(page: OrderRoute.page), AutoRoute(page: SuccessOrderRoute.page), AutoRoute(page: SuccessAddItemOrderRoute.page), + + // Payment + AutoRoute(page: PaymentRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 4136ede..60ec272 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -9,7 +9,7 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:apskel_pos_flutter_v2/domain/order/order.dart' as _i16; +import 'package:apskel_pos_flutter_v2/domain/order/order.dart' as _i17; import 'package:apskel_pos_flutter_v2/presentation/pages/auth/login/login_page.dart' as _i4; import 'package:apskel_pos_flutter_v2/presentation/pages/checkout/checkout_page.dart' @@ -21,97 +21,99 @@ import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/customer/cus import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/home/home_page.dart' as _i3; import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/report/report_page.dart' - as _i7; -import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/setting/setting_page.dart' as _i8; +import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/setting/setting_page.dart' + as _i9; import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/table/table_page.dart' - as _i13; + as _i14; import 'package:apskel_pos_flutter_v2/presentation/pages/order/order_page.dart' as _i6; import 'package:apskel_pos_flutter_v2/presentation/pages/order/pages/success_add_item_order/success_add_item_order_page.dart' - as _i10; -import 'package:apskel_pos_flutter_v2/presentation/pages/order/pages/success_order/success_order_page.dart' as _i11; -import 'package:apskel_pos_flutter_v2/presentation/pages/splash/splash_page.dart' - as _i9; -import 'package:apskel_pos_flutter_v2/presentation/pages/sync/sync_page.dart' +import 'package:apskel_pos_flutter_v2/presentation/pages/order/pages/success_order/success_order_page.dart' as _i12; -import 'package:auto_route/auto_route.dart' as _i14; -import 'package:flutter/material.dart' as _i15; +import 'package:apskel_pos_flutter_v2/presentation/pages/payment/payment_page.dart' + as _i7; +import 'package:apskel_pos_flutter_v2/presentation/pages/splash/splash_page.dart' + as _i10; +import 'package:apskel_pos_flutter_v2/presentation/pages/sync/sync_page.dart' + as _i13; +import 'package:auto_route/auto_route.dart' as _i15; +import 'package:flutter/material.dart' as _i16; /// generated route for /// [_i1.CheckoutPage] -class CheckoutRoute extends _i14.PageRouteInfo { - const CheckoutRoute({List<_i14.PageRouteInfo>? children}) +class CheckoutRoute extends _i15.PageRouteInfo { + const CheckoutRoute({List<_i15.PageRouteInfo>? children}) : super(CheckoutRoute.name, initialChildren: children); static const String name = 'CheckoutRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i1.CheckoutPage()); + return _i15.WrappedRoute(child: const _i1.CheckoutPage()); }, ); } /// generated route for /// [_i2.CustomerPage] -class CustomerRoute extends _i14.PageRouteInfo { - const CustomerRoute({List<_i14.PageRouteInfo>? children}) +class CustomerRoute extends _i15.PageRouteInfo { + const CustomerRoute({List<_i15.PageRouteInfo>? children}) : super(CustomerRoute.name, initialChildren: children); static const String name = 'CustomerRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i2.CustomerPage()); + return _i15.WrappedRoute(child: const _i2.CustomerPage()); }, ); } /// generated route for /// [_i3.HomePage] -class HomeRoute extends _i14.PageRouteInfo { - const HomeRoute({List<_i14.PageRouteInfo>? children}) +class HomeRoute extends _i15.PageRouteInfo { + const HomeRoute({List<_i15.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i3.HomePage()); + return _i15.WrappedRoute(child: const _i3.HomePage()); }, ); } /// generated route for /// [_i4.LoginPage] -class LoginRoute extends _i14.PageRouteInfo { - const LoginRoute({List<_i14.PageRouteInfo>? children}) +class LoginRoute extends _i15.PageRouteInfo { + const LoginRoute({List<_i15.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i4.LoginPage()); + return _i15.WrappedRoute(child: const _i4.LoginPage()); }, ); } /// generated route for /// [_i5.MainPage] -class MainRoute extends _i14.PageRouteInfo { - const MainRoute({List<_i14.PageRouteInfo>? children}) +class MainRoute extends _i15.PageRouteInfo { + const MainRoute({List<_i15.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { return const _i5.MainPage(); @@ -121,11 +123,11 @@ class MainRoute extends _i14.PageRouteInfo { /// generated route for /// [_i6.OrderPage] -class OrderRoute extends _i14.PageRouteInfo { +class OrderRoute extends _i15.PageRouteInfo { OrderRoute({ - _i15.Key? key, + _i16.Key? key, required String status, - List<_i14.PageRouteInfo>? children, + List<_i15.PageRouteInfo>? children, }) : super( OrderRoute.name, args: OrderRouteArgs(key: key, status: status), @@ -134,11 +136,11 @@ class OrderRoute extends _i14.PageRouteInfo { static const String name = 'OrderRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i14.WrappedRoute( + return _i15.WrappedRoute( child: _i6.OrderPage(key: args.key, status: args.status), ); }, @@ -148,7 +150,7 @@ class OrderRoute extends _i14.PageRouteInfo { class OrderRouteArgs { const OrderRouteArgs({this.key, required this.status}); - final _i15.Key? key; + final _i16.Key? key; final String status; @@ -159,76 +161,115 @@ class OrderRouteArgs { } /// generated route for -/// [_i7.ReportPage] -class ReportRoute extends _i14.PageRouteInfo { - const ReportRoute({List<_i14.PageRouteInfo>? children}) +/// [_i7.PaymentPage] +class PaymentRoute extends _i15.PageRouteInfo { + PaymentRoute({ + _i16.Key? key, + required _i17.Order order, + List<_i15.PageRouteInfo>? children, + }) : super( + PaymentRoute.name, + args: PaymentRouteArgs(key: key, order: order), + initialChildren: children, + ); + + static const String name = 'PaymentRoute'; + + static _i15.PageInfo page = _i15.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i15.WrappedRoute( + child: _i7.PaymentPage(key: args.key, order: args.order), + ); + }, + ); +} + +class PaymentRouteArgs { + const PaymentRouteArgs({this.key, required this.order}); + + final _i16.Key? key; + + final _i17.Order order; + + @override + String toString() { + return 'PaymentRouteArgs{key: $key, order: $order}'; + } +} + +/// generated route for +/// [_i8.ReportPage] +class ReportRoute extends _i15.PageRouteInfo { + const ReportRoute({List<_i15.PageRouteInfo>? children}) : super(ReportRoute.name, initialChildren: children); static const String name = 'ReportRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return const _i7.ReportPage(); + return const _i8.ReportPage(); }, ); } /// generated route for -/// [_i8.SettingPage] -class SettingRoute extends _i14.PageRouteInfo { - const SettingRoute({List<_i14.PageRouteInfo>? children}) +/// [_i9.SettingPage] +class SettingRoute extends _i15.PageRouteInfo { + const SettingRoute({List<_i15.PageRouteInfo>? children}) : super(SettingRoute.name, initialChildren: children); static const String name = 'SettingRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return const _i8.SettingPage(); + return const _i9.SettingPage(); }, ); } /// generated route for -/// [_i9.SplashPage] -class SplashRoute extends _i14.PageRouteInfo { - const SplashRoute({List<_i14.PageRouteInfo>? children}) +/// [_i10.SplashPage] +class SplashRoute extends _i15.PageRouteInfo { + const SplashRoute({List<_i15.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return const _i9.SplashPage(); + return const _i10.SplashPage(); }, ); } /// generated route for -/// [_i10.SuccessAddItemOrderPage] -class SuccessAddItemOrderRoute extends _i14.PageRouteInfo { - const SuccessAddItemOrderRoute({List<_i14.PageRouteInfo>? children}) +/// [_i11.SuccessAddItemOrderPage] +class SuccessAddItemOrderRoute extends _i15.PageRouteInfo { + const SuccessAddItemOrderRoute({List<_i15.PageRouteInfo>? children}) : super(SuccessAddItemOrderRoute.name, initialChildren: children); static const String name = 'SuccessAddItemOrderRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return const _i10.SuccessAddItemOrderPage(); + return const _i11.SuccessAddItemOrderPage(); }, ); } /// generated route for -/// [_i11.SuccessOrderPage] -class SuccessOrderRoute extends _i14.PageRouteInfo { +/// [_i12.SuccessOrderPage] +class SuccessOrderRoute extends _i15.PageRouteInfo { SuccessOrderRoute({ - _i15.Key? key, - required _i16.Order order, - List<_i14.PageRouteInfo>? children, + _i16.Key? key, + required _i17.Order order, + List<_i15.PageRouteInfo>? children, }) : super( SuccessOrderRoute.name, args: SuccessOrderRouteArgs(key: key, order: order), @@ -237,12 +278,12 @@ class SuccessOrderRoute extends _i14.PageRouteInfo { static const String name = 'SuccessOrderRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i14.WrappedRoute( - child: _i11.SuccessOrderPage(key: args.key, order: args.order), + return _i15.WrappedRoute( + child: _i12.SuccessOrderPage(key: args.key, order: args.order), ); }, ); @@ -251,9 +292,9 @@ class SuccessOrderRoute extends _i14.PageRouteInfo { class SuccessOrderRouteArgs { const SuccessOrderRouteArgs({this.key, required this.order}); - final _i15.Key? key; + final _i16.Key? key; - final _i16.Order order; + final _i17.Order order; @override String toString() { @@ -262,33 +303,33 @@ class SuccessOrderRouteArgs { } /// generated route for -/// [_i12.SyncPage] -class SyncRoute extends _i14.PageRouteInfo { - const SyncRoute({List<_i14.PageRouteInfo>? children}) +/// [_i13.SyncPage] +class SyncRoute extends _i15.PageRouteInfo { + const SyncRoute({List<_i15.PageRouteInfo>? children}) : super(SyncRoute.name, initialChildren: children); static const String name = 'SyncRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i12.SyncPage()); + return _i15.WrappedRoute(child: const _i13.SyncPage()); }, ); } /// generated route for -/// [_i13.TablePage] -class TableRoute extends _i14.PageRouteInfo { - const TableRoute({List<_i14.PageRouteInfo>? children}) +/// [_i14.TablePage] +class TableRoute extends _i15.PageRouteInfo { + const TableRoute({List<_i15.PageRouteInfo>? children}) : super(TableRoute.name, initialChildren: children); static const String name = 'TableRoute'; - static _i14.PageInfo page = _i14.PageInfo( + static _i15.PageInfo page = _i15.PageInfo( name, builder: (data) { - return _i14.WrappedRoute(child: const _i13.TablePage()); + return _i15.WrappedRoute(child: const _i14.TablePage()); }, ); }