feat: refund page
This commit is contained in:
parent
2912a438e3
commit
1b1d01c1e8
@ -527,4 +527,44 @@ class OrderRemoteDatasource {
|
|||||||
return const Left('Terjadi kesalahan tak terduga');
|
return const Left('Terjadi kesalahan tak terduga');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Either<String, bool>> refundPayment({
|
||||||
|
required String paymentId,
|
||||||
|
required String reason,
|
||||||
|
required int refundAmount,
|
||||||
|
}) async {
|
||||||
|
final authData = await AuthLocalDataSource().getAuthData();
|
||||||
|
final url = '${Variables.baseUrl}/api/v1/payments/$paymentId/refund';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await dio.post(
|
||||||
|
url,
|
||||||
|
data: {
|
||||||
|
'refund_amount': refundAmount,
|
||||||
|
'reason': reason,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ${authData.token}',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return Right(true);
|
||||||
|
} else {
|
||||||
|
return const Left('Gagal refund');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final errorMessage = e.response?.data['message'] ?? 'Kesalahan jaringan';
|
||||||
|
log("💥 Dio error: ${e.message}");
|
||||||
|
log("💥 Dio response: ${e.response?.data}");
|
||||||
|
return Left(errorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
log("💥 Unexpected error: $e");
|
||||||
|
return const Left('Terjadi kesalahan tak terduga');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_
|
|||||||
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
|
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart';
|
import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
|
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
|
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
|
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart';
|
import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart';
|
||||||
@ -256,6 +257,9 @@ class _MyAppState extends State<MyApp> {
|
|||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => VoidOrderBloc(OrderRemoteDatasource()),
|
create: (context) => VoidOrderBloc(OrderRemoteDatasource()),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => RefundBloc(OrderRemoteDatasource()),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
|||||||
40
lib/presentation/refund/bloc/refund_bloc.dart
Normal file
40
lib/presentation/refund/bloc/refund_bloc.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'refund_event.dart';
|
||||||
|
part 'refund_state.dart';
|
||||||
|
part 'refund_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class RefundBloc extends Bloc<RefundEvent, RefundState> {
|
||||||
|
final OrderRemoteDatasource _orderRemoteDatasource;
|
||||||
|
|
||||||
|
RefundBloc(this._orderRemoteDatasource) : super(const RefundState.initial()) {
|
||||||
|
on<RefundEvent>((event, emit) async {
|
||||||
|
await event.when(
|
||||||
|
refundPayment: (paymentId, reason, refundAmount) =>
|
||||||
|
_onRefundPayment(paymentId, reason, refundAmount, emit),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onRefundPayment(
|
||||||
|
String paymentId,
|
||||||
|
String reason,
|
||||||
|
int refundAmount,
|
||||||
|
Emitter<RefundState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const RefundState.loading());
|
||||||
|
|
||||||
|
final result = await _orderRemoteDatasource.refundPayment(
|
||||||
|
paymentId: paymentId,
|
||||||
|
reason: reason,
|
||||||
|
refundAmount: refundAmount,
|
||||||
|
);
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(error) => emit(RefundState.error(error)),
|
||||||
|
(success) => emit(const RefundState.success()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
855
lib/presentation/refund/bloc/refund_bloc.freezed.dart
Normal file
855
lib/presentation/refund/bloc/refund_bloc.freezed.dart
Normal file
@ -0,0 +1,855 @@
|
|||||||
|
// 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 'refund_bloc.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(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 _$RefundEvent {
|
||||||
|
String get paymentId => throw _privateConstructorUsedError;
|
||||||
|
String get reason => throw _privateConstructorUsedError;
|
||||||
|
int get refundAmount => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function(String paymentId, String reason, int refundAmount)
|
||||||
|
refundPayment,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(String paymentId, String reason, int refundAmount)?
|
||||||
|
refundPayment,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(String paymentId, String reason, int refundAmount)?
|
||||||
|
refundPayment,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RefundPayment value) refundPayment,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RefundPayment value)? refundPayment,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RefundPayment value)? refundPayment,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of RefundEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$RefundEventCopyWith<RefundEvent> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $RefundEventCopyWith<$Res> {
|
||||||
|
factory $RefundEventCopyWith(
|
||||||
|
RefundEvent value, $Res Function(RefundEvent) then) =
|
||||||
|
_$RefundEventCopyWithImpl<$Res, RefundEvent>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String paymentId, String reason, int refundAmount});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$RefundEventCopyWithImpl<$Res, $Val extends RefundEvent>
|
||||||
|
implements $RefundEventCopyWith<$Res> {
|
||||||
|
_$RefundEventCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RefundEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? paymentId = null,
|
||||||
|
Object? reason = null,
|
||||||
|
Object? refundAmount = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
paymentId: null == paymentId
|
||||||
|
? _value.paymentId
|
||||||
|
: paymentId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
reason: null == reason
|
||||||
|
? _value.reason
|
||||||
|
: reason // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
refundAmount: null == refundAmount
|
||||||
|
? _value.refundAmount
|
||||||
|
: refundAmount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$RefundPaymentImplCopyWith<$Res>
|
||||||
|
implements $RefundEventCopyWith<$Res> {
|
||||||
|
factory _$$RefundPaymentImplCopyWith(
|
||||||
|
_$RefundPaymentImpl value, $Res Function(_$RefundPaymentImpl) then) =
|
||||||
|
__$$RefundPaymentImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String paymentId, String reason, int refundAmount});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$RefundPaymentImplCopyWithImpl<$Res>
|
||||||
|
extends _$RefundEventCopyWithImpl<$Res, _$RefundPaymentImpl>
|
||||||
|
implements _$$RefundPaymentImplCopyWith<$Res> {
|
||||||
|
__$$RefundPaymentImplCopyWithImpl(
|
||||||
|
_$RefundPaymentImpl _value, $Res Function(_$RefundPaymentImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of RefundEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? paymentId = null,
|
||||||
|
Object? reason = null,
|
||||||
|
Object? refundAmount = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$RefundPaymentImpl(
|
||||||
|
paymentId: null == paymentId
|
||||||
|
? _value.paymentId
|
||||||
|
: paymentId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
reason: null == reason
|
||||||
|
? _value.reason
|
||||||
|
: reason // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
refundAmount: null == refundAmount
|
||||||
|
? _value.refundAmount
|
||||||
|
: refundAmount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$RefundPaymentImpl implements _RefundPayment {
|
||||||
|
const _$RefundPaymentImpl(
|
||||||
|
{required this.paymentId,
|
||||||
|
required this.reason,
|
||||||
|
required this.refundAmount});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String paymentId;
|
||||||
|
@override
|
||||||
|
final String reason;
|
||||||
|
@override
|
||||||
|
final int refundAmount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RefundEvent.refundPayment(paymentId: $paymentId, reason: $reason, refundAmount: $refundAmount)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$RefundPaymentImpl &&
|
||||||
|
(identical(other.paymentId, paymentId) ||
|
||||||
|
other.paymentId == paymentId) &&
|
||||||
|
(identical(other.reason, reason) || other.reason == reason) &&
|
||||||
|
(identical(other.refundAmount, refundAmount) ||
|
||||||
|
other.refundAmount == refundAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, paymentId, reason, refundAmount);
|
||||||
|
|
||||||
|
/// Create a copy of RefundEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$RefundPaymentImplCopyWith<_$RefundPaymentImpl> get copyWith =>
|
||||||
|
__$$RefundPaymentImplCopyWithImpl<_$RefundPaymentImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function(String paymentId, String reason, int refundAmount)
|
||||||
|
refundPayment,
|
||||||
|
}) {
|
||||||
|
return refundPayment(paymentId, reason, refundAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(String paymentId, String reason, int refundAmount)?
|
||||||
|
refundPayment,
|
||||||
|
}) {
|
||||||
|
return refundPayment?.call(paymentId, reason, refundAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(String paymentId, String reason, int refundAmount)?
|
||||||
|
refundPayment,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (refundPayment != null) {
|
||||||
|
return refundPayment(paymentId, reason, refundAmount);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RefundPayment value) refundPayment,
|
||||||
|
}) {
|
||||||
|
return refundPayment(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RefundPayment value)? refundPayment,
|
||||||
|
}) {
|
||||||
|
return refundPayment?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RefundPayment value)? refundPayment,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (refundPayment != null) {
|
||||||
|
return refundPayment(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _RefundPayment implements RefundEvent {
|
||||||
|
const factory _RefundPayment(
|
||||||
|
{required final String paymentId,
|
||||||
|
required final String reason,
|
||||||
|
required final int refundAmount}) = _$RefundPaymentImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentId;
|
||||||
|
@override
|
||||||
|
String get reason;
|
||||||
|
@override
|
||||||
|
int get refundAmount;
|
||||||
|
|
||||||
|
/// Create a copy of RefundEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$RefundPaymentImplCopyWith<_$RefundPaymentImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$RefundState {
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() initial,
|
||||||
|
required TResult Function() loading,
|
||||||
|
required TResult Function() success,
|
||||||
|
required TResult Function(String message) error,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? initial,
|
||||||
|
TResult? Function()? loading,
|
||||||
|
TResult? Function()? success,
|
||||||
|
TResult? Function(String message)? error,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? initial,
|
||||||
|
TResult Function()? loading,
|
||||||
|
TResult Function()? success,
|
||||||
|
TResult Function(String message)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Initial value) initial,
|
||||||
|
required TResult Function(_Loading value) loading,
|
||||||
|
required TResult Function(_Success value) success,
|
||||||
|
required TResult Function(_Error value) error,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Initial value)? initial,
|
||||||
|
TResult? Function(_Loading value)? loading,
|
||||||
|
TResult? Function(_Success value)? success,
|
||||||
|
TResult? Function(_Error value)? error,
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Initial value)? initial,
|
||||||
|
TResult Function(_Loading value)? loading,
|
||||||
|
TResult Function(_Success value)? success,
|
||||||
|
TResult Function(_Error value)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $RefundStateCopyWith<$Res> {
|
||||||
|
factory $RefundStateCopyWith(
|
||||||
|
RefundState value, $Res Function(RefundState) then) =
|
||||||
|
_$RefundStateCopyWithImpl<$Res, RefundState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$RefundStateCopyWithImpl<$Res, $Val extends RefundState>
|
||||||
|
implements $RefundStateCopyWith<$Res> {
|
||||||
|
_$RefundStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$InitialImplCopyWith<$Res> {
|
||||||
|
factory _$$InitialImplCopyWith(
|
||||||
|
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
|
||||||
|
__$$InitialImplCopyWithImpl<$Res>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$InitialImplCopyWithImpl<$Res>
|
||||||
|
extends _$RefundStateCopyWithImpl<$Res, _$InitialImpl>
|
||||||
|
implements _$$InitialImplCopyWith<$Res> {
|
||||||
|
__$$InitialImplCopyWithImpl(
|
||||||
|
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$InitialImpl implements _Initial {
|
||||||
|
const _$InitialImpl();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RefundState.initial()';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType && other is _$InitialImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() initial,
|
||||||
|
required TResult Function() loading,
|
||||||
|
required TResult Function() success,
|
||||||
|
required TResult Function(String message) error,
|
||||||
|
}) {
|
||||||
|
return initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? initial,
|
||||||
|
TResult? Function()? loading,
|
||||||
|
TResult? Function()? success,
|
||||||
|
TResult? Function(String message)? error,
|
||||||
|
}) {
|
||||||
|
return initial?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? initial,
|
||||||
|
TResult Function()? loading,
|
||||||
|
TResult Function()? success,
|
||||||
|
TResult Function(String message)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (initial != null) {
|
||||||
|
return initial();
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Initial value) initial,
|
||||||
|
required TResult Function(_Loading value) loading,
|
||||||
|
required TResult Function(_Success value) success,
|
||||||
|
required TResult Function(_Error value) error,
|
||||||
|
}) {
|
||||||
|
return initial(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Initial value)? initial,
|
||||||
|
TResult? Function(_Loading value)? loading,
|
||||||
|
TResult? Function(_Success value)? success,
|
||||||
|
TResult? Function(_Error value)? error,
|
||||||
|
}) {
|
||||||
|
return initial?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Initial value)? initial,
|
||||||
|
TResult Function(_Loading value)? loading,
|
||||||
|
TResult Function(_Success value)? success,
|
||||||
|
TResult Function(_Error value)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (initial != null) {
|
||||||
|
return initial(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Initial implements RefundState {
|
||||||
|
const factory _Initial() = _$InitialImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LoadingImplCopyWith<$Res> {
|
||||||
|
factory _$$LoadingImplCopyWith(
|
||||||
|
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
|
||||||
|
__$$LoadingImplCopyWithImpl<$Res>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LoadingImplCopyWithImpl<$Res>
|
||||||
|
extends _$RefundStateCopyWithImpl<$Res, _$LoadingImpl>
|
||||||
|
implements _$$LoadingImplCopyWith<$Res> {
|
||||||
|
__$$LoadingImplCopyWithImpl(
|
||||||
|
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$LoadingImpl implements _Loading {
|
||||||
|
const _$LoadingImpl();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RefundState.loading()';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType && other is _$LoadingImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() initial,
|
||||||
|
required TResult Function() loading,
|
||||||
|
required TResult Function() success,
|
||||||
|
required TResult Function(String message) error,
|
||||||
|
}) {
|
||||||
|
return loading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? initial,
|
||||||
|
TResult? Function()? loading,
|
||||||
|
TResult? Function()? success,
|
||||||
|
TResult? Function(String message)? error,
|
||||||
|
}) {
|
||||||
|
return loading?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? initial,
|
||||||
|
TResult Function()? loading,
|
||||||
|
TResult Function()? success,
|
||||||
|
TResult Function(String message)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (loading != null) {
|
||||||
|
return loading();
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Initial value) initial,
|
||||||
|
required TResult Function(_Loading value) loading,
|
||||||
|
required TResult Function(_Success value) success,
|
||||||
|
required TResult Function(_Error value) error,
|
||||||
|
}) {
|
||||||
|
return loading(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Initial value)? initial,
|
||||||
|
TResult? Function(_Loading value)? loading,
|
||||||
|
TResult? Function(_Success value)? success,
|
||||||
|
TResult? Function(_Error value)? error,
|
||||||
|
}) {
|
||||||
|
return loading?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Initial value)? initial,
|
||||||
|
TResult Function(_Loading value)? loading,
|
||||||
|
TResult Function(_Success value)? success,
|
||||||
|
TResult Function(_Error value)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (loading != null) {
|
||||||
|
return loading(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Loading implements RefundState {
|
||||||
|
const factory _Loading() = _$LoadingImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SuccessImplCopyWith<$Res> {
|
||||||
|
factory _$$SuccessImplCopyWith(
|
||||||
|
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
|
||||||
|
__$$SuccessImplCopyWithImpl<$Res>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SuccessImplCopyWithImpl<$Res>
|
||||||
|
extends _$RefundStateCopyWithImpl<$Res, _$SuccessImpl>
|
||||||
|
implements _$$SuccessImplCopyWith<$Res> {
|
||||||
|
__$$SuccessImplCopyWithImpl(
|
||||||
|
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$SuccessImpl implements _Success {
|
||||||
|
const _$SuccessImpl();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RefundState.success()';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType && other is _$SuccessImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() initial,
|
||||||
|
required TResult Function() loading,
|
||||||
|
required TResult Function() success,
|
||||||
|
required TResult Function(String message) error,
|
||||||
|
}) {
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? initial,
|
||||||
|
TResult? Function()? loading,
|
||||||
|
TResult? Function()? success,
|
||||||
|
TResult? Function(String message)? error,
|
||||||
|
}) {
|
||||||
|
return success?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? initial,
|
||||||
|
TResult Function()? loading,
|
||||||
|
TResult Function()? success,
|
||||||
|
TResult Function(String message)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (success != null) {
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Initial value) initial,
|
||||||
|
required TResult Function(_Loading value) loading,
|
||||||
|
required TResult Function(_Success value) success,
|
||||||
|
required TResult Function(_Error value) error,
|
||||||
|
}) {
|
||||||
|
return success(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Initial value)? initial,
|
||||||
|
TResult? Function(_Loading value)? loading,
|
||||||
|
TResult? Function(_Success value)? success,
|
||||||
|
TResult? Function(_Error value)? error,
|
||||||
|
}) {
|
||||||
|
return success?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Initial value)? initial,
|
||||||
|
TResult Function(_Loading value)? loading,
|
||||||
|
TResult Function(_Success value)? success,
|
||||||
|
TResult Function(_Error value)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (success != null) {
|
||||||
|
return success(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Success implements RefundState {
|
||||||
|
const factory _Success() = _$SuccessImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ErrorImplCopyWith<$Res> {
|
||||||
|
factory _$$ErrorImplCopyWith(
|
||||||
|
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
|
||||||
|
__$$ErrorImplCopyWithImpl<$Res>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String message});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ErrorImplCopyWithImpl<$Res>
|
||||||
|
extends _$RefundStateCopyWithImpl<$Res, _$ErrorImpl>
|
||||||
|
implements _$$ErrorImplCopyWith<$Res> {
|
||||||
|
__$$ErrorImplCopyWithImpl(
|
||||||
|
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? message = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$ErrorImpl(
|
||||||
|
null == message
|
||||||
|
? _value.message
|
||||||
|
: message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$ErrorImpl implements _Error {
|
||||||
|
const _$ErrorImpl(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RefundState.error(message: $message)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ErrorImpl &&
|
||||||
|
(identical(other.message, message) || other.message == message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, message);
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
|
||||||
|
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() initial,
|
||||||
|
required TResult Function() loading,
|
||||||
|
required TResult Function() success,
|
||||||
|
required TResult Function(String message) error,
|
||||||
|
}) {
|
||||||
|
return error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? initial,
|
||||||
|
TResult? Function()? loading,
|
||||||
|
TResult? Function()? success,
|
||||||
|
TResult? Function(String message)? error,
|
||||||
|
}) {
|
||||||
|
return error?.call(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? initial,
|
||||||
|
TResult Function()? loading,
|
||||||
|
TResult Function()? success,
|
||||||
|
TResult Function(String message)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (error != null) {
|
||||||
|
return error(message);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Initial value) initial,
|
||||||
|
required TResult Function(_Loading value) loading,
|
||||||
|
required TResult Function(_Success value) success,
|
||||||
|
required TResult Function(_Error value) error,
|
||||||
|
}) {
|
||||||
|
return error(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Initial value)? initial,
|
||||||
|
TResult? Function(_Loading value)? loading,
|
||||||
|
TResult? Function(_Success value)? success,
|
||||||
|
TResult? Function(_Error value)? error,
|
||||||
|
}) {
|
||||||
|
return error?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Initial value)? initial,
|
||||||
|
TResult Function(_Loading value)? loading,
|
||||||
|
TResult Function(_Success value)? success,
|
||||||
|
TResult Function(_Error value)? error,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (error != null) {
|
||||||
|
return error(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Error implements RefundState {
|
||||||
|
const factory _Error(final String message) = _$ErrorImpl;
|
||||||
|
|
||||||
|
String get message;
|
||||||
|
|
||||||
|
/// Create a copy of RefundState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
10
lib/presentation/refund/bloc/refund_event.dart
Normal file
10
lib/presentation/refund/bloc/refund_event.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
part of 'refund_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class RefundEvent with _$RefundEvent {
|
||||||
|
const factory RefundEvent.refundPayment({
|
||||||
|
required String paymentId,
|
||||||
|
required String reason,
|
||||||
|
required int refundAmount,
|
||||||
|
}) = _RefundPayment;
|
||||||
|
}
|
||||||
9
lib/presentation/refund/bloc/refund_state.dart
Normal file
9
lib/presentation/refund/bloc/refund_state.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
part of 'refund_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class RefundState with _$RefundState {
|
||||||
|
const factory RefundState.initial() = _Initial;
|
||||||
|
const factory RefundState.loading() = _Loading;
|
||||||
|
const factory RefundState.success() = _Success;
|
||||||
|
const factory RefundState.error(String message) = _Error;
|
||||||
|
}
|
||||||
90
lib/presentation/refund/dialog/refund_error_dialog.dart
Normal file
90
lib/presentation/refund/dialog/refund_error_dialog.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundErrorDialog extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
const RefundErrorDialog({super.key, required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.red[50]!, Colors.white],
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.red.withOpacity(0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.error,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(24),
|
||||||
|
Text(
|
||||||
|
'Error!',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.red[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(12),
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SpaceHeight(32),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red[600],
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'OK',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
131
lib/presentation/refund/dialog/refund_success_dialog.dart
Normal file
131
lib/presentation/refund/dialog/refund_success_dialog.dart
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||||
|
import 'package:enaklo_pos/core/extensions/int_ext.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundSuccessDialog extends StatelessWidget {
|
||||||
|
final int refundAmount;
|
||||||
|
final String selectedReason;
|
||||||
|
const RefundSuccessDialog(
|
||||||
|
{super.key, required this.refundAmount, required this.selectedReason});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.green[50]!, Colors.white],
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.green.withOpacity(0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(24),
|
||||||
|
Text(
|
||||||
|
'Refund Berhasil!',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(12),
|
||||||
|
Text(
|
||||||
|
'Refund sebesar ${(refundAmount).currencyFormatRpV2} telah diproses.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SpaceHeight(8),
|
||||||
|
Text(
|
||||||
|
'Alasan: $selectedReason',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[500],
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SpaceHeight(32),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: BorderSide(color: AppColors.primary),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Print Receipt',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.primary,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Selesai',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
818
lib/presentation/refund/pages/refund_page.dart
Normal file
818
lib/presentation/refund/pages/refund_page.dart
Normal file
@ -0,0 +1,818 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||||
|
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||||
|
import 'package:enaklo_pos/core/extensions/int_ext.dart';
|
||||||
|
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/dialog/refund_error_dialog.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/dialog/refund_success_dialog.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/widgets/refund_appbar.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/widgets/refund_info_tile.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/widgets/refund_order_Item_tile.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/widgets/refund_reason_tile.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class RefundPage extends StatefulWidget {
|
||||||
|
final Order selectedOrder;
|
||||||
|
|
||||||
|
const RefundPage({super.key, required this.selectedOrder});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RefundPage> createState() => _RefundPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
|
||||||
|
final TextEditingController _reasonController = TextEditingController();
|
||||||
|
final TextEditingController _refundAmountController = TextEditingController();
|
||||||
|
final ScrollController _leftPanelScrollController = ScrollController();
|
||||||
|
final ScrollController _rightPanelScrollController = ScrollController();
|
||||||
|
final ScrollController _itemsScrollController = ScrollController();
|
||||||
|
|
||||||
|
String selectedReason = 'Barang Rusak';
|
||||||
|
|
||||||
|
late AnimationController _slideController;
|
||||||
|
late AnimationController _fadeController;
|
||||||
|
late AnimationController _scaleController;
|
||||||
|
late Animation<Offset> _slideAnimation;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> refundReasons = [
|
||||||
|
{'value': 'Barang Rusak', 'icon': Icons.broken_image, 'color': Colors.red},
|
||||||
|
{'value': 'Salah Item', 'icon': Icons.swap_horiz, 'color': Colors.orange},
|
||||||
|
{
|
||||||
|
'value': 'Tidak Sesuai Pesanan',
|
||||||
|
'icon': Icons.error_outline,
|
||||||
|
'color': Colors.amber
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 'Permintaan Customer',
|
||||||
|
'icon': Icons.person,
|
||||||
|
'color': Colors.blue
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 'Kualitas Tidak Baik',
|
||||||
|
'icon': Icons.thumb_down,
|
||||||
|
'color': Colors.purple
|
||||||
|
},
|
||||||
|
{'value': 'Lainnya', 'icon': Icons.more_horiz, 'color': Colors.grey},
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initializeAnimations();
|
||||||
|
_refundAmountController.text =
|
||||||
|
(widget.selectedOrder.totalAmount ?? 0).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeAnimations() {
|
||||||
|
_slideController = AnimationController(
|
||||||
|
duration: Duration(milliseconds: 1200),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_fadeController = AnimationController(
|
||||||
|
duration: Duration(milliseconds: 800),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_scaleController = AnimationController(
|
||||||
|
duration: Duration(milliseconds: 600),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_slideAnimation = Tween<Offset>(
|
||||||
|
begin: Offset(0.0, 1.0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _slideController,
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
));
|
||||||
|
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _fadeController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 0.8,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _scaleController,
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
));
|
||||||
|
|
||||||
|
_fadeController.forward();
|
||||||
|
_slideController.forward();
|
||||||
|
_scaleController.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_slideController.dispose();
|
||||||
|
_fadeController.dispose();
|
||||||
|
_scaleController.dispose();
|
||||||
|
_reasonController.dispose();
|
||||||
|
_refundAmountController.dispose();
|
||||||
|
_leftPanelScrollController.dispose();
|
||||||
|
_rightPanelScrollController.dispose();
|
||||||
|
_itemsScrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<RefundBloc, RefundState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
state.when(
|
||||||
|
initial: () {},
|
||||||
|
loading: () {},
|
||||||
|
success: () {
|
||||||
|
_showSuccessDialog();
|
||||||
|
},
|
||||||
|
error: (message) {
|
||||||
|
_showErrorDialog(message);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Color(0xFFF5F7FA),
|
||||||
|
body: FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
RefundAppbar(
|
||||||
|
order: widget.selectedOrder,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(24.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Left Panel - Order Summary (Scrollable)
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: _leftPanelScrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _leftPanelScrollController,
|
||||||
|
child: _buildOrderSummaryPanel(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 24),
|
||||||
|
|
||||||
|
// Right Panel - Refund Configuration (Scrollable)
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: _rightPanelScrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _rightPanelScrollController,
|
||||||
|
child: _buildRefundConfigPanel(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOrderSummaryPanel() {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: _scaleAnimation,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Order Info Card
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 30,
|
||||||
|
offset: Offset(0, 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.green[400]!, Colors.green[600]!],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.green.withOpacity(0.3),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.receipt_long,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Detail Pesanan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(4),
|
||||||
|
Text(
|
||||||
|
(widget.selectedOrder.createdAt ?? DateTime.now())
|
||||||
|
.toFormattedDate3(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(20),
|
||||||
|
|
||||||
|
// Order Details Grid
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: RefundInfoTile(
|
||||||
|
title: 'Meja',
|
||||||
|
value: widget.selectedOrder.tableNumber ?? 'Takeaway',
|
||||||
|
icon: Icons.table_restaurant,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: RefundInfoTile(
|
||||||
|
title: 'Tipe',
|
||||||
|
value: widget.selectedOrder.orderType ?? 'N/A',
|
||||||
|
icon: Icons.shopping_bag_outlined,
|
||||||
|
color: Colors.purple,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(16),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: RefundInfoTile(
|
||||||
|
title: 'Status',
|
||||||
|
value: widget.selectedOrder.status?.toUpperCase() ??
|
||||||
|
'N/A',
|
||||||
|
icon: Icons.check_circle_outline,
|
||||||
|
color: _getStatusColor(widget.selectedOrder.status),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: RefundInfoTile(
|
||||||
|
title: 'Items',
|
||||||
|
value:
|
||||||
|
'${widget.selectedOrder.orderItems?.length ?? 0}',
|
||||||
|
icon: Icons.inventory_2_outlined,
|
||||||
|
color: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(24),
|
||||||
|
|
||||||
|
// Payment Summary Card
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 30,
|
||||||
|
offset: Offset(0, 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.account_balance_wallet,
|
||||||
|
color: Colors.amber[700],
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
'Ringkasan Pembayaran',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SpaceHeight(24),
|
||||||
|
_buildPaymentRow(
|
||||||
|
'Subtotal', widget.selectedOrder.subtotal ?? 0),
|
||||||
|
_buildPaymentRow(
|
||||||
|
'Pajak', widget.selectedOrder.taxAmount ?? 0),
|
||||||
|
_buildPaymentRow(
|
||||||
|
'Diskon', -(widget.selectedOrder.discountAmount ?? 0)),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Divider(thickness: 2, color: Colors.grey[200]),
|
||||||
|
),
|
||||||
|
_buildPaymentRow(
|
||||||
|
'Total Dibayar',
|
||||||
|
widget.selectedOrder.totalAmount ?? 0,
|
||||||
|
isTotal: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(24), // Extra space for scroll
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRefundConfigPanel(BuildContext context) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: _slideAnimation,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Refund Reason Card
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 30,
|
||||||
|
offset: Offset(0, 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.red[400]!, Colors.red[600]!],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.red.withOpacity(0.3),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.assignment_return,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
Text(
|
||||||
|
'Konfigurasi Refund',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(20),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
'Pilih Alasan Refund',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Reason Selection Grid
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
childAspectRatio: 2.5,
|
||||||
|
),
|
||||||
|
itemCount: refundReasons.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final reason = refundReasons[index];
|
||||||
|
final isSelected = selectedReason == reason['value'];
|
||||||
|
|
||||||
|
return RefundReasonTile(
|
||||||
|
isSelected: isSelected,
|
||||||
|
reason: reason,
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedReason = reason['value'];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
if (selectedReason == 'Lainnya') ...[
|
||||||
|
SpaceHeight(24),
|
||||||
|
TextField(
|
||||||
|
controller: _reasonController,
|
||||||
|
maxLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Jelaskan alasan refund secara detail...',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: BorderSide(color: Colors.grey[300]!),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: AppColors.primary, width: 2),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey[50],
|
||||||
|
contentPadding: EdgeInsets.all(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
SpaceHeight(32),
|
||||||
|
|
||||||
|
// Refund Amount Input
|
||||||
|
Text(
|
||||||
|
'Jumlah Refund',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(16),
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
controller: _refundAmountController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
readOnly: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Masukkan jumlah refund',
|
||||||
|
prefixText: 'Rp ',
|
||||||
|
prefixStyle: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: BorderSide(color: Colors.grey[300]!),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: AppColors.primary, width: 2),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey[50],
|
||||||
|
contentPadding: EdgeInsets.all(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(24),
|
||||||
|
|
||||||
|
// Items Display Card
|
||||||
|
Container(
|
||||||
|
height: 500, // Fixed height untuk items list
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 30,
|
||||||
|
offset: Offset(0, 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.list_alt,
|
||||||
|
color: Colors.blue[700],
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Item Pesanan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${widget.selectedOrder.orderItems?.length ?? 0} item',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Scrollable Items List
|
||||||
|
Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: _itemsScrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _itemsScrollController,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||||
|
itemCount: widget.selectedOrder.orderItems?.length ?? 0,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = widget.selectedOrder.orderItems![index];
|
||||||
|
return RefundOrderItemTile(item: item);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Process Refund Button
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[50],
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(24),
|
||||||
|
bottomRight: Radius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<RefundBloc, RefundState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final isLoading = state.maybeWhen(
|
||||||
|
loading: () => true,
|
||||||
|
orElse: () => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 64,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
isLoading ? null : () => _processRefund(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red[600],
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
shadowColor: Colors.red.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
strokeWidth: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
'Memproses Refund...',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.monetization_on,
|
||||||
|
color: Colors.white, size: 28),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
'PROSES REFUND',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SpaceHeight(24), // Extra space for scroll
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPaymentRow(String label, int amount, {bool isTotal = false}) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: isTotal ? 18 : 16,
|
||||||
|
fontWeight: isTotal ? FontWeight.bold : FontWeight.w500,
|
||||||
|
color: isTotal ? AppColors.primary : Colors.grey[700],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
amount.currencyFormatRpV2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: isTotal ? 18 : 16,
|
||||||
|
fontWeight: isTotal ? FontWeight.bold : FontWeight.w600,
|
||||||
|
color: isTotal ? AppColors.primary : Colors.grey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getStatusColor(String? status) {
|
||||||
|
switch (status?.toLowerCase()) {
|
||||||
|
case 'completed':
|
||||||
|
case 'paid':
|
||||||
|
return Colors.green;
|
||||||
|
case 'pending':
|
||||||
|
return Colors.orange;
|
||||||
|
case 'cancelled':
|
||||||
|
return Colors.red;
|
||||||
|
default:
|
||||||
|
return Colors.grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _processRefund(BuildContext context) {
|
||||||
|
// Validate refund amount
|
||||||
|
final refundAmount = int.tryParse(_refundAmountController.text) ?? 0;
|
||||||
|
if (refundAmount <= 0) {
|
||||||
|
_showErrorDialog('Jumlah refund harus lebih dari 0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalAmount = widget.selectedOrder.totalAmount ?? 0;
|
||||||
|
if (refundAmount > totalAmount) {
|
||||||
|
_showErrorDialog('Jumlah refund tidak boleh melebihi total Pesanan');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get reason text
|
||||||
|
String reason = selectedReason;
|
||||||
|
if (selectedReason == 'Lainnya' && _reasonController.text.isNotEmpty) {
|
||||||
|
reason = _reasonController.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger refund event
|
||||||
|
context.read<RefundBloc>().add(
|
||||||
|
RefundEvent.refundPayment(
|
||||||
|
paymentId: widget.selectedOrder.id ??
|
||||||
|
'', // Assuming order ID is payment ID
|
||||||
|
reason: reason,
|
||||||
|
refundAmount: refundAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showErrorDialog(String message) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => RefundErrorDialog(message: message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSuccessDialog() {
|
||||||
|
final refundAmount = int.tryParse(_refundAmountController.text) ?? 0;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => RefundSuccessDialog(
|
||||||
|
selectedReason: selectedReason,
|
||||||
|
refundAmount: refundAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
104
lib/presentation/refund/widgets/refund_appbar.dart
Normal file
104
lib/presentation/refund/widgets/refund_appbar.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||||
|
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundAppbar extends StatelessWidget {
|
||||||
|
final Order order;
|
||||||
|
const RefundAppbar({super.key, required this.order});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 120,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Color(0xFF36175E),
|
||||||
|
Color(0xFF4A2C6B),
|
||||||
|
Color(0xFF5D3F78),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color(0xFF36175E).withOpacity(0.4),
|
||||||
|
blurRadius: 30,
|
||||||
|
offset: Offset(0, 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.white.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back_ios_new,
|
||||||
|
color: Colors.white, size: 24),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Refund Pesanan',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Order #${order.orderNumber}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withOpacity(0.9),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
border: Border.all(color: Colors.white.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.schedule, color: Colors.white, size: 18),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
DateTime.now().toFormattedDate3(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
lib/presentation/refund/widgets/refund_info_tile.dart
Normal file
57
lib/presentation/refund/widgets/refund_info_tile.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundInfoTile extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String value;
|
||||||
|
final IconData icon;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const RefundInfoTile({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
required this.icon,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: color.withOpacity(0.2)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 20),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SpaceHeight(8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
130
lib/presentation/refund/widgets/refund_order_Item_tile.dart
Normal file
130
lib/presentation/refund/widgets/refund_order_Item_tile.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||||
|
import 'package:enaklo_pos/core/extensions/int_ext.dart';
|
||||||
|
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundOrderItemTile extends StatelessWidget {
|
||||||
|
final OrderItem item;
|
||||||
|
const RefundOrderItemTile({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[50],
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.grey[200]!, width: 1),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Item Icon
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.restaurant,
|
||||||
|
color: AppColors.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 20),
|
||||||
|
|
||||||
|
// Item Details
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.productName ?? 'N/A',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.productVariantName != null) ...[
|
||||||
|
SpaceHeight(4),
|
||||||
|
Text(
|
||||||
|
item.productVariantName!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (item.notes != null && item.notes!.isNotEmpty) ...[
|
||||||
|
SpaceHeight(8),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Catatan: ${item.notes}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.amber[700],
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Price & Quantity
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
(item.unitPrice ?? 0).currencyFormatRpV2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(4),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'x${item.quantity}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue[700],
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceHeight(8),
|
||||||
|
Text(
|
||||||
|
(item.totalPrice ?? 0).currencyFormatRpV2,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
lib/presentation/refund/widgets/refund_reason_tile.dart
Normal file
53
lib/presentation/refund/widgets/refund_reason_tile.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RefundReasonTile extends StatelessWidget {
|
||||||
|
final bool isSelected;
|
||||||
|
final Map<String, dynamic> reason;
|
||||||
|
final Function() onTap;
|
||||||
|
const RefundReasonTile({
|
||||||
|
super.key,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.reason,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isSelected ? reason['color'].withOpacity(0.2) : Colors.grey[100],
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? reason['color'] : Colors.transparent,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
reason['icon'],
|
||||||
|
color: isSelected ? reason['color'] : Colors.grey[600],
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
SpaceHeight(4),
|
||||||
|
Text(
|
||||||
|
reason['value'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: isSelected ? reason['color'] : Colors.grey[600],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import 'package:enaklo_pos/core/components/buttons.dart';
|
import 'package:enaklo_pos/core/components/buttons.dart';
|
||||||
import 'package:enaklo_pos/core/components/flushbar.dart';
|
|
||||||
import 'package:enaklo_pos/core/components/spaces.dart';
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
||||||
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
|
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
|
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/refund/pages/refund_page.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
|
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
|
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/dialog/payment_dialog.dart';
|
import 'package:enaklo_pos/presentation/sales/dialog/payment_dialog.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/dialog/refund_dialog.dart';
|
|
||||||
import 'package:enaklo_pos/presentation/void/pages/void_page.dart';
|
import 'package:enaklo_pos/presentation/void/pages/void_page.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';
|
import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';
|
||||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart';
|
import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart';
|
||||||
@ -214,37 +213,14 @@ class _SalesPageState extends State<SalesPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (widget.status == 'completed')
|
if (widget.status == 'completed')
|
||||||
BlocBuilder<OrderFormBloc, OrderFormState>(
|
Button.outlined(
|
||||||
builder: (context, state) {
|
onPressed: () {
|
||||||
return state.maybeWhen(
|
context.push(RefundPage(
|
||||||
orElse: () => Button.outlined(
|
selectedOrder: orderDetail!,
|
||||||
onPressed: () {},
|
));
|
||||||
label: 'Refund',
|
|
||||||
icon: Icon(Icons.autorenew),
|
|
||||||
),
|
|
||||||
loaded: (order, selectedItems,
|
|
||||||
totalVoidOrRefund, isAllSelected) =>
|
|
||||||
Button.outlined(
|
|
||||||
onPressed: () {
|
|
||||||
if (selectedItems.isEmpty) {
|
|
||||||
AppFlushbar.showError(context,
|
|
||||||
'Silahkan pilih item yang ingin di refund.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => RefundDialog(
|
|
||||||
order: orderDetail!,
|
|
||||||
selectedItems: selectedItems,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
label: 'Refund',
|
|
||||||
icon: Icon(Icons.autorenew),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
label: 'Refund',
|
||||||
|
icon: Icon(Icons.autorenew),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user