feat: order

This commit is contained in:
efrilm 2025-08-18 14:24:15 +07:00
parent 62eb15b27f
commit d3f2cb300c
19 changed files with 6669 additions and 277 deletions

View File

@ -0,0 +1,90 @@
import 'package:dartz/dartz.dart' hide Order;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import '../../../domain/order/order.dart';
part 'order_loader_event.dart';
part 'order_loader_state.dart';
part 'order_loader_bloc.freezed.dart';
@injectable
class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
final IOrderRepository _repository;
OrderLoaderBloc(this._repository) : super(OrderLoaderState.initial()) {
on<OrderLoaderEvent>(_onOrderLoaderEvent);
}
Future<void> _onOrderLoaderEvent(
OrderLoaderEvent event,
Emitter<OrderLoaderState> emit,
) {
return event.map(
statusChanged: (e) async {
emit(state.copyWith(status: e.status));
},
searchChanged: (e) async {
emit(state.copyWith(search: e.search));
},
fetched: (e) async {
var newState = state;
if (e.isRefresh) {
newState = state.copyWith(isFetching: true);
emit(newState);
}
newState = await _mapFetchedToState(state, isRefresh: e.isRefresh);
emit(newState);
},
);
}
Future<OrderLoaderState> _mapFetchedToState(
OrderLoaderState state, {
bool isRefresh = false,
}) async {
state = state.copyWith(isFetching: false);
if (state.hasReachedMax && state.orders.isNotEmpty && !isRefresh) {
return state;
}
if (isRefresh) {
state = state.copyWith(
page: 1,
failureOptionOrder: none(),
hasReachedMax: false,
orders: [],
);
}
final failureOrOrder = await _repository.get(
status: state.status,
page: state.page,
search: state.search,
);
state = failureOrOrder.fold(
(f) {
if (state.orders.isNotEmpty) {
return state.copyWith(hasReachedMax: true);
}
return state.copyWith(failureOptionOrder: optionOf(f));
},
(orders) {
return state.copyWith(
orders: List.from(state.orders)..addAll(orders),
failureOptionOrder: none(),
page: state.page + 1,
hasReachedMax: orders.length < 10,
);
},
);
return state;
}
}

View File

@ -0,0 +1,815 @@
// 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 'order_loader_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 _$OrderLoaderEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String status) statusChanged,
required TResult Function(String search) searchChanged,
required TResult Function(bool isRefresh) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String status)? statusChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String status)? statusChanged,
TResult Function(String search)? searchChanged,
TResult Function(bool isRefresh)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StatusChanged value) statusChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_Fetched value) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StatusChanged value)? statusChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StatusChanged value)? statusChanged,
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $OrderLoaderEventCopyWith<$Res> {
factory $OrderLoaderEventCopyWith(
OrderLoaderEvent value,
$Res Function(OrderLoaderEvent) then,
) = _$OrderLoaderEventCopyWithImpl<$Res, OrderLoaderEvent>;
}
/// @nodoc
class _$OrderLoaderEventCopyWithImpl<$Res, $Val extends OrderLoaderEvent>
implements $OrderLoaderEventCopyWith<$Res> {
_$OrderLoaderEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$StatusChangedImplCopyWith<$Res> {
factory _$$StatusChangedImplCopyWith(
_$StatusChangedImpl value,
$Res Function(_$StatusChangedImpl) then,
) = __$$StatusChangedImplCopyWithImpl<$Res>;
@useResult
$Res call({String status});
}
/// @nodoc
class __$$StatusChangedImplCopyWithImpl<$Res>
extends _$OrderLoaderEventCopyWithImpl<$Res, _$StatusChangedImpl>
implements _$$StatusChangedImplCopyWith<$Res> {
__$$StatusChangedImplCopyWithImpl(
_$StatusChangedImpl _value,
$Res Function(_$StatusChangedImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? status = null}) {
return _then(
_$StatusChangedImpl(
null == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
class _$StatusChangedImpl implements _StatusChanged {
const _$StatusChangedImpl(this.status);
@override
final String status;
@override
String toString() {
return 'OrderLoaderEvent.statusChanged(status: $status)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$StatusChangedImpl &&
(identical(other.status, status) || other.status == status));
}
@override
int get hashCode => Object.hash(runtimeType, status);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$StatusChangedImplCopyWith<_$StatusChangedImpl> get copyWith =>
__$$StatusChangedImplCopyWithImpl<_$StatusChangedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String status) statusChanged,
required TResult Function(String search) searchChanged,
required TResult Function(bool isRefresh) fetched,
}) {
return statusChanged(status);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String status)? statusChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return statusChanged?.call(status);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String status)? statusChanged,
TResult Function(String search)? searchChanged,
TResult Function(bool isRefresh)? fetched,
required TResult orElse(),
}) {
if (statusChanged != null) {
return statusChanged(status);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StatusChanged value) statusChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_Fetched value) fetched,
}) {
return statusChanged(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StatusChanged value)? statusChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return statusChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StatusChanged value)? statusChanged,
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) {
if (statusChanged != null) {
return statusChanged(this);
}
return orElse();
}
}
abstract class _StatusChanged implements OrderLoaderEvent {
const factory _StatusChanged(final String status) = _$StatusChangedImpl;
String get status;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$StatusChangedImplCopyWith<_$StatusChangedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SearchChangedImplCopyWith<$Res> {
factory _$$SearchChangedImplCopyWith(
_$SearchChangedImpl value,
$Res Function(_$SearchChangedImpl) then,
) = __$$SearchChangedImplCopyWithImpl<$Res>;
@useResult
$Res call({String search});
}
/// @nodoc
class __$$SearchChangedImplCopyWithImpl<$Res>
extends _$OrderLoaderEventCopyWithImpl<$Res, _$SearchChangedImpl>
implements _$$SearchChangedImplCopyWith<$Res> {
__$$SearchChangedImplCopyWithImpl(
_$SearchChangedImpl _value,
$Res Function(_$SearchChangedImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? search = null}) {
return _then(
_$SearchChangedImpl(
null == search
? _value.search
: search // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
class _$SearchChangedImpl implements _SearchChanged {
const _$SearchChangedImpl(this.search);
@override
final String search;
@override
String toString() {
return 'OrderLoaderEvent.searchChanged(search: $search)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SearchChangedImpl &&
(identical(other.search, search) || other.search == search));
}
@override
int get hashCode => Object.hash(runtimeType, search);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SearchChangedImplCopyWith<_$SearchChangedImpl> get copyWith =>
__$$SearchChangedImplCopyWithImpl<_$SearchChangedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String status) statusChanged,
required TResult Function(String search) searchChanged,
required TResult Function(bool isRefresh) fetched,
}) {
return searchChanged(search);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String status)? statusChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return searchChanged?.call(search);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String status)? statusChanged,
TResult Function(String search)? searchChanged,
TResult Function(bool isRefresh)? fetched,
required TResult orElse(),
}) {
if (searchChanged != null) {
return searchChanged(search);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StatusChanged value) statusChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_Fetched value) fetched,
}) {
return searchChanged(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StatusChanged value)? statusChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return searchChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StatusChanged value)? statusChanged,
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) {
if (searchChanged != null) {
return searchChanged(this);
}
return orElse();
}
}
abstract class _SearchChanged implements OrderLoaderEvent {
const factory _SearchChanged(final String search) = _$SearchChangedImpl;
String get search;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SearchChangedImplCopyWith<_$SearchChangedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$FetchedImplCopyWith<$Res> {
factory _$$FetchedImplCopyWith(
_$FetchedImpl value,
$Res Function(_$FetchedImpl) then,
) = __$$FetchedImplCopyWithImpl<$Res>;
@useResult
$Res call({bool isRefresh});
}
/// @nodoc
class __$$FetchedImplCopyWithImpl<$Res>
extends _$OrderLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
implements _$$FetchedImplCopyWith<$Res> {
__$$FetchedImplCopyWithImpl(
_$FetchedImpl _value,
$Res Function(_$FetchedImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? isRefresh = null}) {
return _then(
_$FetchedImpl(
isRefresh: null == isRefresh
? _value.isRefresh
: isRefresh // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$FetchedImpl implements _Fetched {
const _$FetchedImpl({this.isRefresh = false});
@override
@JsonKey()
final bool isRefresh;
@override
String toString() {
return 'OrderLoaderEvent.fetched(isRefresh: $isRefresh)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FetchedImpl &&
(identical(other.isRefresh, isRefresh) ||
other.isRefresh == isRefresh));
}
@override
int get hashCode => Object.hash(runtimeType, isRefresh);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
__$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String status) statusChanged,
required TResult Function(String search) searchChanged,
required TResult Function(bool isRefresh) fetched,
}) {
return fetched(isRefresh);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String status)? statusChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return fetched?.call(isRefresh);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String status)? statusChanged,
TResult Function(String search)? searchChanged,
TResult Function(bool isRefresh)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(isRefresh);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StatusChanged value) statusChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_Fetched value) fetched,
}) {
return fetched(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StatusChanged value)? statusChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return fetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StatusChanged value)? statusChanged,
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(this);
}
return orElse();
}
}
abstract class _Fetched implements OrderLoaderEvent {
const factory _Fetched({final bool isRefresh}) = _$FetchedImpl;
bool get isRefresh;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$OrderLoaderState {
List<Order> get orders => throw _privateConstructorUsedError;
Option<OrderFailure> get failureOptionOrder =>
throw _privateConstructorUsedError;
String? get status => throw _privateConstructorUsedError;
String? get search => throw _privateConstructorUsedError;
bool get isFetching => throw _privateConstructorUsedError;
bool get hasReachedMax => throw _privateConstructorUsedError;
int get page => throw _privateConstructorUsedError;
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OrderLoaderStateCopyWith<OrderLoaderState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $OrderLoaderStateCopyWith<$Res> {
factory $OrderLoaderStateCopyWith(
OrderLoaderState value,
$Res Function(OrderLoaderState) then,
) = _$OrderLoaderStateCopyWithImpl<$Res, OrderLoaderState>;
@useResult
$Res call({
List<Order> orders,
Option<OrderFailure> failureOptionOrder,
String? status,
String? search,
bool isFetching,
bool hasReachedMax,
int page,
});
}
/// @nodoc
class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState>
implements $OrderLoaderStateCopyWith<$Res> {
_$OrderLoaderStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? orders = null,
Object? failureOptionOrder = null,
Object? status = freezed,
Object? search = freezed,
Object? isFetching = null,
Object? hasReachedMax = null,
Object? page = null,
}) {
return _then(
_value.copyWith(
orders: null == orders
? _value.orders
: orders // ignore: cast_nullable_to_non_nullable
as List<Order>,
failureOptionOrder: null == failureOptionOrder
? _value.failureOptionOrder
: failureOptionOrder // ignore: cast_nullable_to_non_nullable
as Option<OrderFailure>,
status: freezed == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as String?,
search: freezed == search
? _value.search
: search // ignore: cast_nullable_to_non_nullable
as String?,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
hasReachedMax: null == hasReachedMax
? _value.hasReachedMax
: hasReachedMax // ignore: cast_nullable_to_non_nullable
as bool,
page: null == page
? _value.page
: page // ignore: cast_nullable_to_non_nullable
as int,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$OrderLoaderStateImplCopyWith<$Res>
implements $OrderLoaderStateCopyWith<$Res> {
factory _$$OrderLoaderStateImplCopyWith(
_$OrderLoaderStateImpl value,
$Res Function(_$OrderLoaderStateImpl) then,
) = __$$OrderLoaderStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
List<Order> orders,
Option<OrderFailure> failureOptionOrder,
String? status,
String? search,
bool isFetching,
bool hasReachedMax,
int page,
});
}
/// @nodoc
class __$$OrderLoaderStateImplCopyWithImpl<$Res>
extends _$OrderLoaderStateCopyWithImpl<$Res, _$OrderLoaderStateImpl>
implements _$$OrderLoaderStateImplCopyWith<$Res> {
__$$OrderLoaderStateImplCopyWithImpl(
_$OrderLoaderStateImpl _value,
$Res Function(_$OrderLoaderStateImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? orders = null,
Object? failureOptionOrder = null,
Object? status = freezed,
Object? search = freezed,
Object? isFetching = null,
Object? hasReachedMax = null,
Object? page = null,
}) {
return _then(
_$OrderLoaderStateImpl(
orders: null == orders
? _value._orders
: orders // ignore: cast_nullable_to_non_nullable
as List<Order>,
failureOptionOrder: null == failureOptionOrder
? _value.failureOptionOrder
: failureOptionOrder // ignore: cast_nullable_to_non_nullable
as Option<OrderFailure>,
status: freezed == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as String?,
search: freezed == search
? _value.search
: search // ignore: cast_nullable_to_non_nullable
as String?,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
hasReachedMax: null == hasReachedMax
? _value.hasReachedMax
: hasReachedMax // ignore: cast_nullable_to_non_nullable
as bool,
page: null == page
? _value.page
: page // ignore: cast_nullable_to_non_nullable
as int,
),
);
}
}
/// @nodoc
class _$OrderLoaderStateImpl implements _OrderLoaderState {
const _$OrderLoaderStateImpl({
required final List<Order> orders,
required this.failureOptionOrder,
this.status,
this.search,
this.isFetching = false,
this.hasReachedMax = false,
this.page = 1,
}) : _orders = orders;
final List<Order> _orders;
@override
List<Order> get orders {
if (_orders is EqualUnmodifiableListView) return _orders;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_orders);
}
@override
final Option<OrderFailure> failureOptionOrder;
@override
final String? status;
@override
final String? search;
@override
@JsonKey()
final bool isFetching;
@override
@JsonKey()
final bool hasReachedMax;
@override
@JsonKey()
final int page;
@override
String toString() {
return 'OrderLoaderState(orders: $orders, failureOptionOrder: $failureOptionOrder, status: $status, search: $search, isFetching: $isFetching, hasReachedMax: $hasReachedMax, page: $page)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$OrderLoaderStateImpl &&
const DeepCollectionEquality().equals(other._orders, _orders) &&
(identical(other.failureOptionOrder, failureOptionOrder) ||
other.failureOptionOrder == failureOptionOrder) &&
(identical(other.status, status) || other.status == status) &&
(identical(other.search, search) || other.search == search) &&
(identical(other.isFetching, isFetching) ||
other.isFetching == isFetching) &&
(identical(other.hasReachedMax, hasReachedMax) ||
other.hasReachedMax == hasReachedMax) &&
(identical(other.page, page) || other.page == page));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_orders),
failureOptionOrder,
status,
search,
isFetching,
hasReachedMax,
page,
);
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OrderLoaderStateImplCopyWith<_$OrderLoaderStateImpl> get copyWith =>
__$$OrderLoaderStateImplCopyWithImpl<_$OrderLoaderStateImpl>(
this,
_$identity,
);
}
abstract class _OrderLoaderState implements OrderLoaderState {
const factory _OrderLoaderState({
required final List<Order> orders,
required final Option<OrderFailure> failureOptionOrder,
final String? status,
final String? search,
final bool isFetching,
final bool hasReachedMax,
final int page,
}) = _$OrderLoaderStateImpl;
@override
List<Order> get orders;
@override
Option<OrderFailure> get failureOptionOrder;
@override
String? get status;
@override
String? get search;
@override
bool get isFetching;
@override
bool get hasReachedMax;
@override
int get page;
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OrderLoaderStateImplCopyWith<_$OrderLoaderStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,9 @@
part of 'order_loader_bloc.dart';
@freezed
class OrderLoaderEvent with _$OrderLoaderEvent {
const factory OrderLoaderEvent.statusChanged(String status) = _StatusChanged;
const factory OrderLoaderEvent.searchChanged(String search) = _SearchChanged;
const factory OrderLoaderEvent.fetched({@Default(false) bool isRefresh}) =
_Fetched;
}

View File

@ -0,0 +1,17 @@
part of 'order_loader_bloc.dart';
@freezed
class OrderLoaderState with _$OrderLoaderState {
const factory OrderLoaderState({
required List<Order> orders,
required Option<OrderFailure> failureOptionOrder,
String? status,
String? search,
@Default(false) bool isFetching,
@Default(false) bool hasReachedMax,
@Default(1) int page,
}) = _OrderLoaderState;
factory OrderLoaderState.initial() =>
OrderLoaderState(orders: [], failureOptionOrder: none());
}

View File

@ -24,4 +24,7 @@ class ApiPath {
// Customer
static const String customer = '/api/v1/customers';
// Order
static const String order = '/api/v1/orders';
}

View File

@ -0,0 +1,161 @@
part of '../order.dart';
@freezed
class Order with _$Order {
const factory Order({
required String id,
required String orderNumber,
required String outletId,
required String userId,
required String tableNumber,
required String orderType,
required String status,
required int subtotal,
required int taxAmount,
required int discountAmount,
required int totalAmount,
required int totalCost,
required int remainingAmount,
required String paymentStatus,
required int refundAmount,
required bool isVoid,
required bool isRefund,
required String notes,
required Map<String, dynamic> metadata,
required String createdAt,
required String updatedAt,
required List<OrderItem> orderItems,
required List<OrderPayment> payments,
required int totalPaid,
required int paymentCount,
required String splitType,
}) = _Order;
factory Order.empty() => Order(
id: '',
orderNumber: '',
outletId: '',
userId: '',
tableNumber: '',
orderType: '',
status: '',
subtotal: 0,
taxAmount: 0,
discountAmount: 0,
totalAmount: 0,
totalCost: 0,
remainingAmount: 0,
paymentStatus: '',
refundAmount: 0,
isVoid: false,
isRefund: false,
notes: '',
metadata: {},
createdAt: '',
updatedAt: '',
orderItems: [],
payments: [],
totalPaid: 0,
paymentCount: 0,
splitType: '',
);
}
@freezed
class OrderItem with _$OrderItem {
const factory OrderItem({
required String id,
required String orderId,
required String productId,
required String productName,
required int quantity,
required int price,
required int subtotal,
required int discountAmount,
required int total,
required int cost,
required Map<String, dynamic> metadata,
required String createdAt,
required String updatedAt,
}) = _OrderItem;
factory OrderItem.empty() => OrderItem(
id: '',
orderId: '',
productId: '',
productName: '',
quantity: 0,
price: 0,
subtotal: 0,
discountAmount: 0,
total: 0,
cost: 0,
metadata: {},
createdAt: '',
updatedAt: '',
);
}
@freezed
class OrderPayment with _$OrderPayment {
const factory OrderPayment({
required String id,
required String orderId,
required String paymentMethodId,
required String paymentMethodName,
required String paymentMethodType,
required int amount,
required String status,
required int splitNumber,
required int splitTotal,
required String splitType,
required String splitDescription,
required int refundAmount,
required Map<String, dynamic> metadata,
required String createdAt,
required String updatedAt,
required List<PaymentOrderItem> paymentOrderItems,
}) = _OrderPayment;
factory OrderPayment.empty() => OrderPayment(
id: '',
orderId: '',
paymentMethodId: '',
paymentMethodName: '',
paymentMethodType: '',
amount: 0,
status: '',
splitNumber: 0,
splitTotal: 0,
splitType: '',
splitDescription: '',
refundAmount: 0,
metadata: {},
createdAt: '',
updatedAt: '',
paymentOrderItems: [],
);
}
@freezed
class PaymentOrderItem with _$PaymentOrderItem {
const factory PaymentOrderItem({
required String id,
required String orderPaymentId,
required String orderItemId,
required int amount,
required int refundAmount,
required String createdAt,
required String updatedAt,
}) = _PaymentOrderItem;
factory PaymentOrderItem.empty() => PaymentOrderItem(
id: '',
orderPaymentId: '',
orderItemId: '',
amount: 0,
refundAmount: 0,
createdAt: '',
updatedAt: '',
);
}

View File

@ -0,0 +1,10 @@
part of '../order.dart';
@freezed
sealed class OrderFailure with _$OrderFailure {
const factory OrderFailure.serverError(ApiFailure failure) = _ServerError;
const factory OrderFailure.unexpectedError() = _UnexpectedError;
const factory OrderFailure.empty() = _Empty;
const factory OrderFailure.dynamicErrorMessage(String erroMessage) =
_DynamicErrorMessage;
}

View File

@ -0,0 +1,10 @@
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../common/api/api_failure.dart';
part 'order.freezed.dart';
part 'entities/order_entity.dart';
part 'failures/order_failure.dart';
part 'repositories/i_order_repository.dart';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
part of '../order.dart';
abstract class IOrderRepository {
Future<Either<OrderFailure, List<Order>>> get({
int page = 1,
int limit = 10,
String? status,
String? search,
});
}

View File

@ -0,0 +1,57 @@
import 'dart:developer';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/function/app_function.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/order/order.dart';
import '../order_dtos.dart';
@injectable
class OrderRemoteDataProvider {
final ApiClient _apiClient;
final String _logName = 'OrderRemoteDataProvider';
OrderRemoteDataProvider(this._apiClient);
Future<DC<OrderFailure, List<OrderDto>>> fetch({
int page = 1,
int limit = 10,
String? status,
String? search,
}) async {
try {
Map<String, dynamic> params = {'page': page, 'limit': limit};
if (status != null && status.isNotEmpty) {
params['status'] = status;
}
if (search != null && search.isNotEmpty) {
params['search'] = search;
}
final response = await _apiClient.get(
ApiPath.order,
params: params,
headers: getAuthorizationHeader(),
);
if (response.data['data'] == null) {
return DC.error(OrderFailure.empty());
}
final dto = (response.data['data']['orders'] as List)
.map((item) => OrderDto.fromJson(item))
.toList();
return DC.data(dto);
} on ApiFailure catch (e, s) {
log('fetchOrderError', name: _logName, error: e, stackTrace: s);
return DC.error(OrderFailure.serverError(e));
}
}
}

View File

@ -0,0 +1,183 @@
part of '../order_dtos.dart';
@freezed
class OrderDto with _$OrderDto {
const OrderDto._();
const factory OrderDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'order_number') String? orderNumber,
@JsonKey(name: 'outlet_id') String? outletId,
@JsonKey(name: 'user_id') String? userId,
@JsonKey(name: 'table_number') String? tableNumber,
@JsonKey(name: 'order_type') String? orderType,
@JsonKey(name: 'status') String? status,
@JsonKey(name: 'subtotal') int? subtotal,
@JsonKey(name: 'tax_amount') int? taxAmount,
@JsonKey(name: 'discount_amount') int? discountAmount,
@JsonKey(name: 'total_amount') int? totalAmount,
@JsonKey(name: 'total_cost') int? totalCost,
@JsonKey(name: 'remaining_amount') int? remainingAmount,
@JsonKey(name: 'payment_status') String? paymentStatus,
@JsonKey(name: 'refund_amount') int? refundAmount,
@JsonKey(name: 'is_void') bool? isVoid,
@JsonKey(name: 'is_refund') bool? isRefund,
@JsonKey(name: 'notes') String? notes,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
@JsonKey(name: 'order_items') List<OrderItemDto>? orderItems,
@JsonKey(name: 'payments') List<OrderPaymentDto>? payments,
@JsonKey(name: 'total_paid') int? totalPaid,
@JsonKey(name: 'payment_count') int? paymentCount,
@JsonKey(name: 'split_type') String? splitType,
}) = _OrderDto;
factory OrderDto.fromJson(Map<String, dynamic> json) =>
_$OrderDtoFromJson(json);
Order toDomain() => Order(
id: id ?? '',
orderNumber: orderNumber ?? '',
outletId: outletId ?? '',
userId: userId ?? '',
tableNumber: tableNumber ?? '',
orderType: orderType ?? '',
status: status ?? '',
subtotal: subtotal ?? 0,
taxAmount: taxAmount ?? 0,
discountAmount: discountAmount ?? 0,
totalAmount: totalAmount ?? 0,
totalCost: totalCost ?? 0,
remainingAmount: remainingAmount ?? 0,
paymentStatus: paymentStatus ?? '',
refundAmount: refundAmount ?? 0,
isVoid: isVoid ?? false,
isRefund: isRefund ?? false,
notes: notes ?? '',
metadata: metadata ?? {},
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
orderItems: orderItems?.map((e) => e.toDomain()).toList() ?? [],
payments: payments?.map((e) => e.toDomain()).toList() ?? [],
totalPaid: totalPaid ?? 0,
paymentCount: paymentCount ?? 0,
splitType: splitType ?? '',
);
}
@freezed
class OrderItemDto with _$OrderItemDto {
const OrderItemDto._();
const factory OrderItemDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'order_id') String? orderId,
@JsonKey(name: 'product_id') String? productId,
@JsonKey(name: 'product_name') String? productName,
@JsonKey(name: 'quantity') int? quantity,
@JsonKey(name: 'price') int? price,
@JsonKey(name: 'subtotal') int? subtotal,
@JsonKey(name: 'discount_amount') int? discountAmount,
@JsonKey(name: 'total') int? total,
@JsonKey(name: 'cost') int? cost,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
}) = _OrderItemDto;
factory OrderItemDto.fromJson(Map<String, dynamic> json) =>
_$OrderItemDtoFromJson(json);
OrderItem toDomain() => OrderItem(
id: id ?? '',
orderId: orderId ?? '',
productId: productId ?? '',
productName: productName ?? '',
quantity: quantity ?? 0,
price: price ?? 0,
subtotal: subtotal ?? 0,
discountAmount: discountAmount ?? 0,
total: total ?? 0,
cost: cost ?? 0,
metadata: metadata ?? {},
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
);
}
@freezed
class OrderPaymentDto with _$OrderPaymentDto {
const OrderPaymentDto._();
const factory OrderPaymentDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'order_id') String? orderId,
@JsonKey(name: 'payment_method_id') String? paymentMethodId,
@JsonKey(name: 'payment_method_name') String? paymentMethodName,
@JsonKey(name: 'payment_method_type') String? paymentMethodType,
@JsonKey(name: 'amount') int? amount,
@JsonKey(name: 'status') String? status,
@JsonKey(name: 'split_number') int? splitNumber,
@JsonKey(name: 'split_total') int? splitTotal,
@JsonKey(name: 'split_type') String? splitType,
@JsonKey(name: 'split_description') String? splitDescription,
@JsonKey(name: 'refund_amount') int? refundAmount,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
@JsonKey(name: 'payment_order_items')
List<PaymentOrderItemDto>? paymentOrderItems,
}) = _OrderPaymentDto;
factory OrderPaymentDto.fromJson(Map<String, dynamic> json) =>
_$OrderPaymentDtoFromJson(json);
OrderPayment toDomain() => OrderPayment(
id: id ?? '',
orderId: orderId ?? '',
paymentMethodId: paymentMethodId ?? '',
paymentMethodName: paymentMethodName ?? '',
paymentMethodType: paymentMethodType ?? '',
amount: amount ?? 0,
status: status ?? '',
splitNumber: splitNumber ?? 0,
splitTotal: splitTotal ?? 0,
splitType: splitType ?? '',
splitDescription: splitDescription ?? '',
refundAmount: refundAmount ?? 0,
metadata: metadata ?? {},
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
paymentOrderItems:
paymentOrderItems?.map((e) => e.toDomain()).toList() ?? [],
);
}
@freezed
class PaymentOrderItemDto with _$PaymentOrderItemDto {
const PaymentOrderItemDto._();
const factory PaymentOrderItemDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'order_payment_id') String? orderPaymentId,
@JsonKey(name: 'order_item_id') String? orderItemId,
@JsonKey(name: 'amount') int? amount,
@JsonKey(name: 'refund_amount') int? refundAmount,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
}) = _PaymentOrderItemDto;
factory PaymentOrderItemDto.fromJson(Map<String, dynamic> json) =>
_$PaymentOrderItemDtoFromJson(json);
PaymentOrderItem toDomain() => PaymentOrderItem(
id: id ?? '',
orderPaymentId: orderPaymentId ?? '',
orderItemId: orderItemId ?? '',
amount: amount ?? 0,
refundAmount: refundAmount ?? 0,
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
);
}

View File

@ -0,0 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/order/order.dart';
part 'order_dtos.freezed.dart';
part 'order_dtos.g.dart';
part 'dto/order_dto.dart';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'order_dtos.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$OrderDtoImpl _$$OrderDtoImplFromJson(Map<String, dynamic> json) =>
_$OrderDtoImpl(
id: json['id'] as String?,
orderNumber: json['order_number'] as String?,
outletId: json['outlet_id'] as String?,
userId: json['user_id'] as String?,
tableNumber: json['table_number'] as String?,
orderType: json['order_type'] as String?,
status: json['status'] as String?,
subtotal: (json['subtotal'] as num?)?.toInt(),
taxAmount: (json['tax_amount'] as num?)?.toInt(),
discountAmount: (json['discount_amount'] as num?)?.toInt(),
totalAmount: (json['total_amount'] as num?)?.toInt(),
totalCost: (json['total_cost'] as num?)?.toInt(),
remainingAmount: (json['remaining_amount'] as num?)?.toInt(),
paymentStatus: json['payment_status'] as String?,
refundAmount: (json['refund_amount'] as num?)?.toInt(),
isVoid: json['is_void'] as bool?,
isRefund: json['is_refund'] as bool?,
notes: json['notes'] as String?,
metadata: json['metadata'] as Map<String, dynamic>?,
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
orderItems: (json['order_items'] as List<dynamic>?)
?.map((e) => OrderItemDto.fromJson(e as Map<String, dynamic>))
.toList(),
payments: (json['payments'] as List<dynamic>?)
?.map((e) => OrderPaymentDto.fromJson(e as Map<String, dynamic>))
.toList(),
totalPaid: (json['total_paid'] as num?)?.toInt(),
paymentCount: (json['payment_count'] as num?)?.toInt(),
splitType: json['split_type'] as String?,
);
Map<String, dynamic> _$$OrderDtoImplToJson(_$OrderDtoImpl instance) =>
<String, dynamic>{
'id': instance.id,
'order_number': instance.orderNumber,
'outlet_id': instance.outletId,
'user_id': instance.userId,
'table_number': instance.tableNumber,
'order_type': instance.orderType,
'status': instance.status,
'subtotal': instance.subtotal,
'tax_amount': instance.taxAmount,
'discount_amount': instance.discountAmount,
'total_amount': instance.totalAmount,
'total_cost': instance.totalCost,
'remaining_amount': instance.remainingAmount,
'payment_status': instance.paymentStatus,
'refund_amount': instance.refundAmount,
'is_void': instance.isVoid,
'is_refund': instance.isRefund,
'notes': instance.notes,
'metadata': instance.metadata,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
'order_items': instance.orderItems,
'payments': instance.payments,
'total_paid': instance.totalPaid,
'payment_count': instance.paymentCount,
'split_type': instance.splitType,
};
_$OrderItemDtoImpl _$$OrderItemDtoImplFromJson(Map<String, dynamic> json) =>
_$OrderItemDtoImpl(
id: json['id'] as String?,
orderId: json['order_id'] as String?,
productId: json['product_id'] as String?,
productName: json['product_name'] as String?,
quantity: (json['quantity'] as num?)?.toInt(),
price: (json['price'] as num?)?.toInt(),
subtotal: (json['subtotal'] as num?)?.toInt(),
discountAmount: (json['discount_amount'] as num?)?.toInt(),
total: (json['total'] as num?)?.toInt(),
cost: (json['cost'] as num?)?.toInt(),
metadata: json['metadata'] as Map<String, dynamic>?,
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
<String, dynamic>{
'id': instance.id,
'order_id': instance.orderId,
'product_id': instance.productId,
'product_name': instance.productName,
'quantity': instance.quantity,
'price': instance.price,
'subtotal': instance.subtotal,
'discount_amount': instance.discountAmount,
'total': instance.total,
'cost': instance.cost,
'metadata': instance.metadata,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
};
_$OrderPaymentDtoImpl _$$OrderPaymentDtoImplFromJson(
Map<String, dynamic> json,
) => _$OrderPaymentDtoImpl(
id: json['id'] as String?,
orderId: json['order_id'] as String?,
paymentMethodId: json['payment_method_id'] as String?,
paymentMethodName: json['payment_method_name'] as String?,
paymentMethodType: json['payment_method_type'] as String?,
amount: (json['amount'] as num?)?.toInt(),
status: json['status'] as String?,
splitNumber: (json['split_number'] as num?)?.toInt(),
splitTotal: (json['split_total'] as num?)?.toInt(),
splitType: json['split_type'] as String?,
splitDescription: json['split_description'] as String?,
refundAmount: (json['refund_amount'] as num?)?.toInt(),
metadata: json['metadata'] as Map<String, dynamic>?,
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
paymentOrderItems: (json['payment_order_items'] as List<dynamic>?)
?.map((e) => PaymentOrderItemDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$OrderPaymentDtoImplToJson(
_$OrderPaymentDtoImpl instance,
) => <String, dynamic>{
'id': instance.id,
'order_id': instance.orderId,
'payment_method_id': instance.paymentMethodId,
'payment_method_name': instance.paymentMethodName,
'payment_method_type': instance.paymentMethodType,
'amount': instance.amount,
'status': instance.status,
'split_number': instance.splitNumber,
'split_total': instance.splitTotal,
'split_type': instance.splitType,
'split_description': instance.splitDescription,
'refund_amount': instance.refundAmount,
'metadata': instance.metadata,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
'payment_order_items': instance.paymentOrderItems,
};
_$PaymentOrderItemDtoImpl _$$PaymentOrderItemDtoImplFromJson(
Map<String, dynamic> json,
) => _$PaymentOrderItemDtoImpl(
id: json['id'] as String?,
orderPaymentId: json['order_payment_id'] as String?,
orderItemId: json['order_item_id'] as String?,
amount: (json['amount'] as num?)?.toInt(),
refundAmount: (json['refund_amount'] as num?)?.toInt(),
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> _$$PaymentOrderItemDtoImplToJson(
_$PaymentOrderItemDtoImpl instance,
) => <String, dynamic>{
'id': instance.id,
'order_payment_id': instance.orderPaymentId,
'order_item_id': instance.orderItemId,
'amount': instance.amount,
'refund_amount': instance.refundAmount,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
};

View File

@ -0,0 +1,43 @@
import 'dart:developer';
import 'package:dartz/dartz.dart' hide Order;
import 'package:injectable/injectable.dart' hide Order;
import '../../../domain/order/order.dart';
import '../datasource/remote_data_provider.dart';
@Injectable(as: IOrderRepository)
class OrderRepository implements IOrderRepository {
final OrderRemoteDataProvider _dataProvider;
final String _logName = 'OrderRepository';
OrderRepository(this._dataProvider);
@override
Future<Either<OrderFailure, List<Order>>> get({
int page = 1,
int limit = 20,
String? status,
String? search,
}) async {
try {
final result = await _dataProvider.fetch(
page: page,
limit: limit,
status: status,
search: search,
);
if (result.hasError) {
return left(result.error!);
}
final auth = result.data!.map((e) => e.toDomain()).toList();
return right(auth);
} catch (e, s) {
log('getOrderError', name: _logName, error: e, stackTrace: s);
return left(const OrderFailure.unexpectedError());
}
}
}

View File

@ -34,6 +34,8 @@ import 'package:apskel_owner_flutter/application/customer/customer_loader/custom
as _i972;
import 'package:apskel_owner_flutter/application/language/language_bloc.dart'
as _i455;
import 'package:apskel_owner_flutter/application/order/order_loader/order_loader_bloc.dart'
as _i1058;
import 'package:apskel_owner_flutter/application/product/product_loader/product_loader_bloc.dart'
as _i458;
import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115;
@ -50,6 +52,7 @@ import 'package:apskel_owner_flutter/domain/analytic/repositories/i_analytic_rep
import 'package:apskel_owner_flutter/domain/auth/auth.dart' as _i49;
import 'package:apskel_owner_flutter/domain/category/category.dart' as _i1020;
import 'package:apskel_owner_flutter/domain/customer/customer.dart' as _i48;
import 'package:apskel_owner_flutter/domain/order/order.dart' as _i219;
import 'package:apskel_owner_flutter/domain/product/product.dart' as _i419;
import 'package:apskel_owner_flutter/env.dart' as _i6;
import 'package:apskel_owner_flutter/infrastructure/analytic/datasource/remote_data_provider.dart'
@ -70,6 +73,10 @@ import 'package:apskel_owner_flutter/infrastructure/customer/datasources/remote_
as _i1006;
import 'package:apskel_owner_flutter/infrastructure/customer/repositories/customer_repository.dart'
as _i550;
import 'package:apskel_owner_flutter/infrastructure/order/datasource/remote_data_provider.dart'
as _i130;
import 'package:apskel_owner_flutter/infrastructure/order/repositories/order_repository.dart'
as _i641;
import 'package:apskel_owner_flutter/infrastructure/product/datasources/remote_data_provider.dart'
as _i823;
import 'package:apskel_owner_flutter/infrastructure/product/repositories/product_repository.dart'
@ -138,9 +145,15 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i1006.CustomerRemoteDataProvider>(
() => _i1006.CustomerRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i130.OrderRemoteDataProvider>(
() => _i130.OrderRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i48.ICustomerRepository>(
() => _i550.CustomerRepository(gh<_i1006.CustomerRemoteDataProvider>()),
);
gh.factory<_i219.IOrderRepository>(
() => _i641.OrderRepository(gh<_i130.OrderRemoteDataProvider>()),
);
gh.factory<_i49.IAuthRepository>(
() => _i1035.AuthRepository(
gh<_i991.AuthLocalDataProvider>(),
@ -200,6 +213,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i574.LogoutFormBloc>(
() => _i574.LogoutFormBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i1058.OrderLoaderBloc>(
() => _i1058.OrderLoaderBloc(gh<_i219.IOrderRepository>()),
);
return this;
}
}

View File

@ -1,20 +1,32 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:line_icons/line_icons.dart';
import '../../../application/order/order_loader/order_loader_bloc.dart';
import '../../../common/theme/theme.dart';
import '../../../injection.dart';
import '../../components/appbar/appbar.dart';
import '../../components/button/button.dart';
import '../../components/spacer/spacer.dart';
import '../../components/widgets/empty_widget.dart';
import 'widgets/status_tile.dart';
import 'widgets/order_tile.dart';
@RoutePage()
class OrderPage extends StatefulWidget {
class OrderPage extends StatefulWidget implements AutoRouteWrapper {
const OrderPage({super.key});
@override
State<OrderPage> createState() => _OrderPageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (_) =>
getIt<OrderLoaderBloc>()
..add(OrderLoaderEvent.fetched(isRefresh: true)),
child: this,
);
}
class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
@ -22,15 +34,11 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
late AnimationController _slideController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
final ScrollController _scrollController = ScrollController();
// Filter state
String selectedFilter = 'All';
final List<String> filterOptions = [
'All',
'Completed',
'Pending',
'Refunded',
];
final List<String> filterOptions = ['All', 'Completed', 'Pending'];
@override
void initState() {
@ -66,70 +74,33 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
super.dispose();
}
final sampleTransactions = [
Transaction(
id: 'TXN001',
customerName: 'John Doe',
date: DateTime.now().subtract(const Duration(hours: 2)),
totalAmount: 125000,
itemCount: 3,
paymentMethod: 'Cash',
status: TransactionStatus.completed,
receiptNumber: 'RCP-2024-001',
),
Transaction(
id: 'TXN002',
customerName: 'Jane Smith',
date: DateTime.now().subtract(const Duration(hours: 5)),
totalAmount: 87500,
itemCount: 2,
paymentMethod: 'QRIS',
status: TransactionStatus.pending,
receiptNumber: 'RCP-2024-002',
),
Transaction(
id: 'TXN003',
customerName: 'Bob Johnson',
date: DateTime.now().subtract(const Duration(days: 1)),
totalAmount: 250000,
itemCount: 5,
paymentMethod: 'Credit Card',
status: TransactionStatus.refunded,
receiptNumber: 'RCP-2024-003',
),
];
// Filter transactions based on selected status
List<Transaction> get filteredTransactions {
if (selectedFilter == 'All') {
return sampleTransactions;
}
TransactionStatus? filterStatus;
switch (selectedFilter) {
case 'Completed':
filterStatus = TransactionStatus.completed;
break;
case 'Pending':
filterStatus = TransactionStatus.pending;
break;
case 'Refunded':
filterStatus = TransactionStatus.refunded;
break;
}
return sampleTransactions
.where((transaction) => transaction.status == filterStatus)
.toList();
}
// Build filter chip
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: CustomScrollView(
body: BlocListener<OrderLoaderBloc, OrderLoaderState>(
listenWhen: (p, c) => p.status != c.status,
listener: (context, state) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(isRefresh: true),
);
},
child: BlocBuilder<OrderLoaderBloc, OrderLoaderState>(
builder: (context, state) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
_scrollController.position.extentAfter == 0) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(),
);
return true;
}
return true;
},
child: CustomScrollView(
controller: _scrollController,
slivers: [
// Custom App Bar with Hero Effect
SliverAppBar(
@ -167,7 +138,9 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
final index = filterOptions.indexOf(option);
return Padding(
padding: EdgeInsets.only(
right: index < filterOptions.length - 1 ? 8 : 0,
right: index < filterOptions.length - 1
? 8
: 0,
),
child: OrderStatusTile(
label: option,
@ -177,6 +150,19 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
setState(() {
selectedFilter = option;
});
if (option.toLowerCase() == 'all') {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.statusChanged(
'',
),
);
} else {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.statusChanged(
option.toLowerCase(),
),
);
}
}
},
),
@ -208,7 +194,7 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
child: Row(
children: [
Text(
'${filteredTransactions.length} ${selectedFilter.toLowerCase()} transaction${filteredTransactions.length != 1 ? 's' : ''}',
'${state.orders.length} ${selectedFilter.toLowerCase()} order${state.orders.length != 1 ? 's' : ''}',
style: TextStyle(
color: AppColor.textSecondary,
fontSize: 14,
@ -219,40 +205,24 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
),
// Transaction List
filteredTransactions.isEmpty
? Container(
padding: const EdgeInsets.symmetric(
vertical: 40,
),
child: Column(
children: [
Icon(
LineIcons.receipt,
size: 64,
color: AppColor.textSecondary.withOpacity(
0.5,
),
),
const SizedBox(height: 16),
Text(
'No ${selectedFilter.toLowerCase()} transactions found',
style: TextStyle(
color: AppColor.textSecondary,
fontSize: 16,
),
),
],
),
state.orders.isEmpty
? EmptyWidget(
title: 'Order',
message:
'No ${selectedFilter.toLowerCase()} orders found',
)
: Column(
children: filteredTransactions.map((
transaction,
) {
: ListView.builder(
itemCount: state.orders.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
return OrderTile(
transaction: transaction,
onTap: () {},
order: state.orders[index],
);
}).toList(),
},
),
],
),
@ -264,6 +234,10 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
],
),
);
},
),
),
);
}
}

View File

@ -2,41 +2,17 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../common/theme/theme.dart';
// Model untuk Transaction
class Transaction {
final String id;
final String customerName;
final DateTime date;
final double totalAmount;
final int itemCount;
final String paymentMethod;
final TransactionStatus status;
final String? receiptNumber;
Transaction({
required this.id,
required this.customerName,
required this.date,
required this.totalAmount,
required this.itemCount,
required this.paymentMethod,
required this.status,
this.receiptNumber,
});
}
enum TransactionStatus { completed, pending, cancelled, refunded }
import '../../../../domain/order/order.dart';
class OrderTile extends StatelessWidget {
final Transaction transaction;
final Order order;
final VoidCallback? onTap;
final VoidCallback? onPrint;
final VoidCallback? onRefund;
const OrderTile({
super.key,
required this.transaction,
required this.order,
this.onTap,
this.onPrint,
this.onRefund,
@ -73,8 +49,8 @@ class OrderTile extends StatelessWidget {
_buildHeaderRow(),
const SizedBox(height: 12),
// Transaction Info
_buildTransactionInfo(),
// Order Info
_buildOrderInfo(),
const SizedBox(height: 16),
// Amount Section
@ -99,7 +75,9 @@ class OrderTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
transaction.receiptNumber ?? 'TXN-${transaction.id}',
order.orderNumber.isNotEmpty
? order.orderNumber
: 'ORD-${order.id}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@ -108,7 +86,7 @@ class OrderTile extends StatelessWidget {
),
const SizedBox(height: 2),
Text(
DateFormat('dd MMM yyyy, HH:mm').format(transaction.date),
_formatDate(order.createdAt),
style: const TextStyle(
fontSize: 12,
color: AppColor.textSecondary,
@ -126,28 +104,39 @@ class OrderTile extends StatelessWidget {
String statusText;
IconData statusIcon;
switch (transaction.status) {
case TransactionStatus.completed:
// Check isVoid and isRefund first for display
if (order.isVoid) {
statusColor = AppColor.error;
statusText = 'Void';
statusIcon = Icons.block;
} else if (order.isRefund) {
statusColor = AppColor.info;
statusText = 'Refunded';
statusIcon = Icons.undo;
} else {
// Handle status values (only pending and completed)
switch (order.status.toLowerCase()) {
case 'completed':
case 'paid':
case 'finished':
statusColor = AppColor.success;
statusText = 'Completed';
statusIcon = Icons.check_circle;
break;
case TransactionStatus.pending:
case 'pending':
case 'waiting':
case 'processing':
statusColor = AppColor.warning;
statusText = 'Pending';
statusIcon = Icons.schedule;
break;
case TransactionStatus.cancelled:
statusColor = AppColor.error;
statusText = 'Cancelled';
statusIcon = Icons.cancel;
break;
case TransactionStatus.refunded:
statusColor = AppColor.info;
statusText = 'Refunded';
statusIcon = Icons.undo;
default:
statusColor = AppColor.textSecondary;
statusText = order.status;
statusIcon = Icons.info;
break;
}
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
@ -174,7 +163,7 @@ class OrderTile extends StatelessWidget {
);
}
Widget _buildTransactionInfo() {
Widget _buildOrderInfo() {
return Row(
children: [
Expanded(
@ -184,11 +173,11 @@ class OrderTile extends StatelessWidget {
children: [
Row(
children: [
Icon(Icons.person_outline, size: 16, color: AppColor.primary),
Icon(_getOrderInfoIcon(), size: 16, color: AppColor.primary),
const SizedBox(width: 6),
Expanded(
child: Text(
transaction.customerName,
_getOrderInfoText(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
@ -209,7 +198,7 @@ class OrderTile extends StatelessWidget {
),
const SizedBox(width: 6),
Text(
'${transaction.itemCount} items',
'${order.orderItems.length} items',
style: const TextStyle(
fontSize: 13,
color: AppColor.textSecondary,
@ -230,13 +219,13 @@ class OrderTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_getPaymentIcon(transaction.paymentMethod),
_getOrderTypeIcon(order.orderType),
size: 16,
color: AppColor.primary,
),
const SizedBox(width: 6),
Text(
transaction.paymentMethod,
order.orderType.isNotEmpty ? order.orderType : 'Dine In',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
@ -285,13 +274,24 @@ class OrderTile extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
'Rp ${NumberFormat('#,###').format(transaction.totalAmount)}',
'Rp ${NumberFormat('#,###').format(order.totalAmount)}',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColor.textWhite,
),
),
if (order.remainingAmount > 0) ...[
const SizedBox(height: 4),
Text(
'Remaining: Rp ${NumberFormat('#,###').format(order.remainingAmount)}',
style: const TextStyle(
fontSize: 12,
color: AppColor.textWhite,
fontWeight: FontWeight.w400,
),
),
],
],
),
Container(
@ -300,8 +300,8 @@ class OrderTile extends StatelessWidget {
color: AppColor.backgroundLight.withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.attach_money,
child: Icon(
_getPaymentStatusIcon(order.paymentStatus),
color: AppColor.textWhite,
size: 24,
),
@ -312,19 +312,32 @@ class OrderTile extends StatelessWidget {
}
Widget _buildFooterActions() {
// Don't show anything if order is void or refunded
if (order.isVoid || order.isRefund) {
return const SizedBox.shrink();
}
return Row(
children: [
Expanded(
child: Text(
'ID: ${transaction.id}',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (order.payments.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
'Payment: ${_getPaymentMethods()}',
style: const TextStyle(
fontSize: 11,
color: AppColor.textLight,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w400,
),
),
],
],
),
if (transaction.status == TransactionStatus.completed) ...[
),
if (order.status.toLowerCase() == 'completed') ...[
_buildActionButton(
icon: Icons.print,
label: 'Print',
@ -377,25 +390,90 @@ class OrderTile extends StatelessWidget {
);
}
IconData _getPaymentIcon(String paymentMethod) {
switch (paymentMethod.toLowerCase()) {
case 'cash':
return Icons.payments;
case 'card':
case 'credit card':
case 'debit card':
return Icons.credit_card;
case 'qris':
case 'qr code':
return Icons.qr_code;
case 'transfer':
case 'bank transfer':
return Icons.account_balance;
case 'e-wallet':
case 'digital wallet':
return Icons.account_balance_wallet;
IconData _getOrderInfoIcon() {
switch (order.orderType.toLowerCase()) {
case 'dine in':
case 'dine_in':
return Icons.table_restaurant_outlined;
case 'takeaway':
case 'take_away':
case 'pickup':
return Icons.person_outline;
case 'delivery':
return Icons.location_on_outlined;
default:
return Icons.payment;
return Icons.receipt_outlined;
}
}
String _getOrderInfoText() {
switch (order.orderType.toLowerCase()) {
case 'dine in':
case 'dine_in':
return 'Table ${order.tableNumber.isNotEmpty ? order.tableNumber : 'N/A'}';
case 'takeaway':
case 'take_away':
case 'pickup':
return 'Pickup Order';
case 'delivery':
return 'Delivery Order';
default:
return order.tableNumber.isNotEmpty
? 'Table ${order.tableNumber}'
: 'Order ${order.orderNumber}';
}
}
IconData _getOrderTypeIcon(String orderType) {
switch (orderType.toLowerCase()) {
case 'dine in':
case 'dine_in':
return Icons.restaurant;
case 'takeaway':
case 'take_away':
case 'pickup':
return Icons.shopping_bag;
case 'delivery':
return Icons.delivery_dining;
default:
return Icons.restaurant_menu;
}
}
IconData _getPaymentStatusIcon(String paymentStatus) {
switch (paymentStatus.toLowerCase()) {
case 'paid':
case 'completed':
return Icons.check_circle;
case 'pending':
case 'partial':
return Icons.schedule;
case 'failed':
case 'cancelled':
return Icons.error;
default:
return Icons.attach_money;
}
}
String _getPaymentMethods() {
if (order.payments.isEmpty) return 'N/A';
// Get unique payment methods from payments
final methods = order.payments
.map((payment) => payment.paymentMethodName)
.toSet()
.join(', ');
return methods.isEmpty ? 'N/A' : methods;
}
String _formatDate(String dateString) {
try {
final date = DateTime.parse(dateString);
return DateFormat('dd MMM yyyy, HH:mm').format(date);
} catch (e) {
return dateString;
}
}
}