checkout form
This commit is contained in:
parent
e85138f27e
commit
013f313e35
BIN
assets/images/gojek.png
Normal file
BIN
assets/images/gojek.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/images/grab.png
Normal file
BIN
assets/images/grab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
168
lib/application/checkout/checkout_form/checkout_form_bloc.dart
Normal file
168
lib/application/checkout/checkout_form/checkout_form_bloc.dart
Normal file
@ -0,0 +1,168 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../common/types/order_type.dart';
|
||||
import '../../../domain/delivery/delivery.dart';
|
||||
import '../../../domain/product/product.dart';
|
||||
|
||||
part 'checkout_form_event.dart';
|
||||
part 'checkout_form_state.dart';
|
||||
part 'checkout_form_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
|
||||
CheckoutFormBloc() : super(CheckoutFormState.initial()) {
|
||||
on<CheckoutFormEvent>(_onCheckoutFormEvent);
|
||||
}
|
||||
Future<void> _onCheckoutFormEvent(
|
||||
CheckoutFormEvent event,
|
||||
Emitter<CheckoutFormState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
addItem: (e) async {
|
||||
final currentState = state;
|
||||
emit(currentState.copyWith(isLoading: true));
|
||||
|
||||
List<ProductQuantity> items = [...currentState.items];
|
||||
|
||||
final index = items.indexWhere(
|
||||
(element) =>
|
||||
element.product.id == e.product.id &&
|
||||
element.variant?.id == e.variant?.id,
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
// Jika sudah ada → tambah quantity
|
||||
items[index] = items[index].copyWith(
|
||||
quantity: items[index].quantity + 1,
|
||||
);
|
||||
} else {
|
||||
// Jika belum ada → tambahkan item baru
|
||||
items.add(
|
||||
ProductQuantity(
|
||||
product: e.product,
|
||||
quantity: 1,
|
||||
variant: e.variant,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
log('🛒 Items updated: ${items.length} items total');
|
||||
|
||||
final totalQuantity = items.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
final totalPrice = items.fold<int>(
|
||||
0,
|
||||
(sum, item) =>
|
||||
sum +
|
||||
(item.quantity *
|
||||
(item.variant?.priceModifier.toInt() ??
|
||||
item.product.price.toInt())),
|
||||
);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
items: items,
|
||||
totalQuantity: totalQuantity,
|
||||
totalPrice: totalPrice,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
removeItem: (e) async {
|
||||
final currentState = state;
|
||||
emit(currentState.copyWith(isLoading: true));
|
||||
List<ProductQuantity> items = [...currentState.items];
|
||||
|
||||
final index = items.indexWhere(
|
||||
(element) =>
|
||||
element.product.id == e.product.id &&
|
||||
element.variant?.id == e.variant?.id,
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
final currentItem = items[index];
|
||||
if (currentItem.quantity > 1) {
|
||||
// Kurangi quantity
|
||||
items[index] = currentItem.copyWith(
|
||||
quantity: currentItem.quantity - 1,
|
||||
);
|
||||
} else {
|
||||
// Hapus item kalau quantity = 1
|
||||
items.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
final totalQuantity = items.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
final totalPrice = items.fold<int>(
|
||||
0,
|
||||
(sum, item) =>
|
||||
sum +
|
||||
(item.quantity *
|
||||
(item.variant?.priceModifier.toInt() ??
|
||||
item.product.price.toInt())),
|
||||
);
|
||||
|
||||
log(
|
||||
'🗑️ Item removed. Total items: ${items.length}, totalQuantity: $totalQuantity, totalPrice: $totalPrice',
|
||||
);
|
||||
|
||||
// Emit state baru
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
items: items,
|
||||
totalQuantity: totalQuantity,
|
||||
totalPrice: totalPrice,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
started: (e) async {
|
||||
emit(CheckoutFormState.initial().copyWith(isLoading: true));
|
||||
try {
|
||||
emit(
|
||||
CheckoutFormState.initial().copyWith(
|
||||
items: e.items,
|
||||
tax: 0,
|
||||
serviceCharge: 0,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// Kalau gagal, pakai default values
|
||||
log('⚠️ Failed to load settings: $e');
|
||||
emit(
|
||||
CheckoutFormState.initial().copyWith(
|
||||
tax: 10,
|
||||
serviceCharge: 5,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
updateItemNotes: (e) async {
|
||||
final currentState = state;
|
||||
|
||||
// Clone list items agar tidak mutasi langsung
|
||||
final items = [...currentState.items];
|
||||
final index = items.indexWhere(
|
||||
(element) => element.product.id == e.product.id,
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
items[index] = items[index].copyWith(notes: e.notes);
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(items: items, isLoading: false));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
part of 'checkout_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CheckoutFormEvent with _$CheckoutFormEvent {
|
||||
const factory CheckoutFormEvent.started(List<ProductQuantity> items) =
|
||||
_Started;
|
||||
|
||||
const factory CheckoutFormEvent.addItem(
|
||||
Product product,
|
||||
ProductVariant? variant,
|
||||
) = _AddItem;
|
||||
|
||||
const factory CheckoutFormEvent.removeItem(
|
||||
Product product,
|
||||
ProductVariant? variant,
|
||||
) = _RemoveItem;
|
||||
|
||||
const factory CheckoutFormEvent.updateItemNotes(
|
||||
Product product,
|
||||
String notes,
|
||||
) = _UpdateItemNotes;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
part of 'checkout_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CheckoutFormState with _$CheckoutFormState {
|
||||
factory CheckoutFormState({
|
||||
required List<ProductQuantity> items,
|
||||
required int discount,
|
||||
required int discountAmount,
|
||||
required int tax,
|
||||
required int serviceCharge,
|
||||
required int totalQuantity,
|
||||
required int totalPrice,
|
||||
required String draftName,
|
||||
required OrderType orderType,
|
||||
Delivery? delivery,
|
||||
@Default(false) bool isLoading,
|
||||
}) = _CheckoutFormState;
|
||||
|
||||
factory CheckoutFormState.initial() => CheckoutFormState(
|
||||
items: [],
|
||||
discount: 0,
|
||||
discountAmount: 0,
|
||||
tax: 0,
|
||||
serviceCharge: 0,
|
||||
totalQuantity: 0,
|
||||
totalPrice: 0,
|
||||
draftName: '',
|
||||
orderType: OrderType.dineIn,
|
||||
);
|
||||
}
|
||||
16
lib/common/types/order_type.dart
Normal file
16
lib/common/types/order_type.dart
Normal file
@ -0,0 +1,16 @@
|
||||
enum OrderType {
|
||||
dineIn('DINE IN'),
|
||||
takeAway('TAKE AWAY'),
|
||||
delivery('DELIVERY'),
|
||||
freeTable('FREE TABLE');
|
||||
|
||||
final String value;
|
||||
const OrderType(this.value);
|
||||
|
||||
static OrderType fromString(String value) {
|
||||
return OrderType.values.firstWhere(
|
||||
(type) => type.value == value,
|
||||
orElse: () => OrderType.dineIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
14
lib/domain/delivery/delivery.dart
Normal file
14
lib/domain/delivery/delivery.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import '../../presentation/components/assets/assets.gen.dart';
|
||||
|
||||
class Delivery {
|
||||
String id;
|
||||
String name;
|
||||
String imageUrl;
|
||||
|
||||
Delivery({required this.id, required this.name, required this.imageUrl});
|
||||
}
|
||||
|
||||
List<Delivery> deliveries = [
|
||||
Delivery(id: 'gojek', name: 'Gojek', imageUrl: Assets.images.gojek.path),
|
||||
Delivery(id: 'grab', name: 'Grab', imageUrl: Assets.images.grab.path),
|
||||
];
|
||||
14
lib/domain/product/entities/product_quantity_entity.dart
Normal file
14
lib/domain/product/entities/product_quantity_entity.dart
Normal file
@ -0,0 +1,14 @@
|
||||
part of '../product.dart';
|
||||
|
||||
@freezed
|
||||
class ProductQuantity with _$ProductQuantity {
|
||||
const factory ProductQuantity({
|
||||
required Product product,
|
||||
ProductVariant? variant,
|
||||
required int quantity,
|
||||
String? notes,
|
||||
}) = _ProductQuantity;
|
||||
|
||||
factory ProductQuantity.empty() =>
|
||||
ProductQuantity(product: Product.empty(), quantity: 0);
|
||||
}
|
||||
@ -7,5 +7,6 @@ import '../../common/api/api_failure.dart';
|
||||
part 'product.freezed.dart';
|
||||
|
||||
part 'entities/product_entity.dart';
|
||||
part 'entities/product_quantity_entity.dart';
|
||||
part 'failures/product_failure.dart';
|
||||
part 'repositories/i_product_repository.dart';
|
||||
|
||||
@ -1092,6 +1092,260 @@ abstract class _ProductVariant implements ProductVariant {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProductQuantity {
|
||||
Product get product => throw _privateConstructorUsedError;
|
||||
ProductVariant? get variant => throw _privateConstructorUsedError;
|
||||
int get quantity => throw _privateConstructorUsedError;
|
||||
String? get notes => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ProductQuantityCopyWith<ProductQuantity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProductQuantityCopyWith<$Res> {
|
||||
factory $ProductQuantityCopyWith(
|
||||
ProductQuantity value,
|
||||
$Res Function(ProductQuantity) then,
|
||||
) = _$ProductQuantityCopyWithImpl<$Res, ProductQuantity>;
|
||||
@useResult
|
||||
$Res call({
|
||||
Product product,
|
||||
ProductVariant? variant,
|
||||
int quantity,
|
||||
String? notes,
|
||||
});
|
||||
|
||||
$ProductCopyWith<$Res> get product;
|
||||
$ProductVariantCopyWith<$Res>? get variant;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProductQuantityCopyWithImpl<$Res, $Val extends ProductQuantity>
|
||||
implements $ProductQuantityCopyWith<$Res> {
|
||||
_$ProductQuantityCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? product = null,
|
||||
Object? variant = freezed,
|
||||
Object? quantity = null,
|
||||
Object? notes = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
product: null == product
|
||||
? _value.product
|
||||
: product // ignore: cast_nullable_to_non_nullable
|
||||
as Product,
|
||||
variant: freezed == variant
|
||||
? _value.variant
|
||||
: variant // ignore: cast_nullable_to_non_nullable
|
||||
as ProductVariant?,
|
||||
quantity: null == quantity
|
||||
? _value.quantity
|
||||
: quantity // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
notes: freezed == notes
|
||||
? _value.notes
|
||||
: notes // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProductCopyWith<$Res> get product {
|
||||
return $ProductCopyWith<$Res>(_value.product, (value) {
|
||||
return _then(_value.copyWith(product: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProductVariantCopyWith<$Res>? get variant {
|
||||
if (_value.variant == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ProductVariantCopyWith<$Res>(_value.variant!, (value) {
|
||||
return _then(_value.copyWith(variant: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProductQuantityImplCopyWith<$Res>
|
||||
implements $ProductQuantityCopyWith<$Res> {
|
||||
factory _$$ProductQuantityImplCopyWith(
|
||||
_$ProductQuantityImpl value,
|
||||
$Res Function(_$ProductQuantityImpl) then,
|
||||
) = __$$ProductQuantityImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
Product product,
|
||||
ProductVariant? variant,
|
||||
int quantity,
|
||||
String? notes,
|
||||
});
|
||||
|
||||
@override
|
||||
$ProductCopyWith<$Res> get product;
|
||||
@override
|
||||
$ProductVariantCopyWith<$Res>? get variant;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProductQuantityImplCopyWithImpl<$Res>
|
||||
extends _$ProductQuantityCopyWithImpl<$Res, _$ProductQuantityImpl>
|
||||
implements _$$ProductQuantityImplCopyWith<$Res> {
|
||||
__$$ProductQuantityImplCopyWithImpl(
|
||||
_$ProductQuantityImpl _value,
|
||||
$Res Function(_$ProductQuantityImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? product = null,
|
||||
Object? variant = freezed,
|
||||
Object? quantity = null,
|
||||
Object? notes = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$ProductQuantityImpl(
|
||||
product: null == product
|
||||
? _value.product
|
||||
: product // ignore: cast_nullable_to_non_nullable
|
||||
as Product,
|
||||
variant: freezed == variant
|
||||
? _value.variant
|
||||
: variant // ignore: cast_nullable_to_non_nullable
|
||||
as ProductVariant?,
|
||||
quantity: null == quantity
|
||||
? _value.quantity
|
||||
: quantity // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
notes: freezed == notes
|
||||
? _value.notes
|
||||
: notes // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProductQuantityImpl
|
||||
with DiagnosticableTreeMixin
|
||||
implements _ProductQuantity {
|
||||
const _$ProductQuantityImpl({
|
||||
required this.product,
|
||||
this.variant,
|
||||
required this.quantity,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
@override
|
||||
final Product product;
|
||||
@override
|
||||
final ProductVariant? variant;
|
||||
@override
|
||||
final int quantity;
|
||||
@override
|
||||
final String? notes;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'ProductQuantity(product: $product, variant: $variant, quantity: $quantity, notes: $notes)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'ProductQuantity'))
|
||||
..add(DiagnosticsProperty('product', product))
|
||||
..add(DiagnosticsProperty('variant', variant))
|
||||
..add(DiagnosticsProperty('quantity', quantity))
|
||||
..add(DiagnosticsProperty('notes', notes));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProductQuantityImpl &&
|
||||
(identical(other.product, product) || other.product == product) &&
|
||||
(identical(other.variant, variant) || other.variant == variant) &&
|
||||
(identical(other.quantity, quantity) ||
|
||||
other.quantity == quantity) &&
|
||||
(identical(other.notes, notes) || other.notes == notes));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, product, variant, quantity, notes);
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProductQuantityImplCopyWith<_$ProductQuantityImpl> get copyWith =>
|
||||
__$$ProductQuantityImplCopyWithImpl<_$ProductQuantityImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class _ProductQuantity implements ProductQuantity {
|
||||
const factory _ProductQuantity({
|
||||
required final Product product,
|
||||
final ProductVariant? variant,
|
||||
required final int quantity,
|
||||
final String? notes,
|
||||
}) = _$ProductQuantityImpl;
|
||||
|
||||
@override
|
||||
Product get product;
|
||||
@override
|
||||
ProductVariant? get variant;
|
||||
@override
|
||||
int get quantity;
|
||||
@override
|
||||
String? get notes;
|
||||
|
||||
/// Create a copy of ProductQuantity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ProductQuantityImplCopyWith<_$ProductQuantityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProductFailure {
|
||||
@optionalTypeArgs
|
||||
|
||||
@ -14,6 +14,8 @@ import 'package:apskel_pos_flutter_v2/application/auth/login_form/login_form_blo
|
||||
as _i46;
|
||||
import 'package:apskel_pos_flutter_v2/application/category/category_loader/category_loader_bloc.dart'
|
||||
as _i1018;
|
||||
import 'package:apskel_pos_flutter_v2/application/checkout/checkout_form/checkout_form_bloc.dart'
|
||||
as _i13;
|
||||
import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart'
|
||||
as _i76;
|
||||
import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart'
|
||||
@ -86,6 +88,7 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
() => sharedPreferencesDi.prefs,
|
||||
preResolve: true,
|
||||
);
|
||||
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
|
||||
gh.singleton<_i487.DatabaseHelper>(() => databaseDi.databaseHelper);
|
||||
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
|
||||
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
|
||||
@ -116,12 +119,12 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.factory<_i370.AuthRemoteDataProvider>(
|
||||
() => _i370.AuthRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||
);
|
||||
gh.factory<_i132.OutletRemoteDataProvider>(
|
||||
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||
);
|
||||
gh.factory<_i707.ProductRemoteDataProvider>(
|
||||
() => _i707.ProductRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||
);
|
||||
gh.factory<_i132.OutletRemoteDataProvider>(
|
||||
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||
);
|
||||
gh.factory<_i776.IAuthRepository>(
|
||||
() => _i941.AuthRepository(
|
||||
gh<_i370.AuthRemoteDataProvider>(),
|
||||
|
||||
@ -14,6 +14,12 @@ import 'package:flutter/widgets.dart';
|
||||
class $AssetsImagesGen {
|
||||
const $AssetsImagesGen();
|
||||
|
||||
/// File path: assets/images/gojek.png
|
||||
AssetGenImage get gojek => const AssetGenImage('assets/images/gojek.png');
|
||||
|
||||
/// File path: assets/images/grab.png
|
||||
AssetGenImage get grab => const AssetGenImage('assets/images/grab.png');
|
||||
|
||||
/// File path: assets/images/logo.png
|
||||
AssetGenImage get logo => const AssetGenImage('assets/images/logo.png');
|
||||
|
||||
@ -22,7 +28,7 @@ class $AssetsImagesGen {
|
||||
const AssetGenImage('assets/images/logo_white.png');
|
||||
|
||||
/// List of all assets
|
||||
List<AssetGenImage> get values => [logo, logoWhite];
|
||||
List<AssetGenImage> get values => [gojek, grab, logo, logoWhite];
|
||||
}
|
||||
|
||||
class Assets {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user