From 935b6b9a5b976794ec511db9465b4f555eafa85d Mon Sep 17 00:00:00 2001 From: efrilm Date: Tue, 5 Aug 2025 15:25:10 +0700 Subject: [PATCH] feat: update outlet --- .../datasources/user_remote_datasource.dart | 47 + .../models/response/auth_response_model.dart | 2 +- lib/main.dart | 5 + .../user_update_outlet_bloc.dart | 32 + .../user_update_outlet_bloc.freezed.dart | 811 ++++++++++++++++ .../user_update_outlet_event.dart | 6 + .../user_update_outlet_state.dart | 9 + .../home/dialog/outlet_dialog.dart | 55 +- lib/presentation/home/pages/home_page.dart | 889 +++++++++--------- .../home/widgets/outlet_card.dart | 6 +- 10 files changed, 1421 insertions(+), 441 deletions(-) create mode 100644 lib/data/datasources/user_remote_datasource.dart create mode 100644 lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart create mode 100644 lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart create mode 100644 lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart create mode 100644 lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart diff --git a/lib/data/datasources/user_remote_datasource.dart b/lib/data/datasources/user_remote_datasource.dart new file mode 100644 index 0000000..c86c820 --- /dev/null +++ b/lib/data/datasources/user_remote_datasource.dart @@ -0,0 +1,47 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/constants/variables.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; +import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; +import 'package:enaklo_pos/data/models/response/auth_response_model.dart'; + +class UserRemoteDatasource { + final Dio dio = DioClient.instance; + + Future> updateOutlet(String outletId) async { + AuthResponseModel authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/users/${authData.user?.id}'; + + try { + final response = await dio.put( + url, + data: { + 'outlet_id': outletId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + authData.user?.outletId = response.data['outlet_id']; + await AuthLocalDataSource().saveAuthData(authData); + return Right(true); + } else { + return const Left('Failed to login'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Login gagal'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/models/response/auth_response_model.dart b/lib/data/models/response/auth_response_model.dart index ef7a840..ff787f2 100644 --- a/lib/data/models/response/auth_response_model.dart +++ b/lib/data/models/response/auth_response_model.dart @@ -29,7 +29,7 @@ class AuthResponseModel { class User { final String? id; final String? organizationId; - final String? outletId; + String? outletId; final String? name; final String? email; final String? role; diff --git a/lib/main.dart b/lib/main.dart index be2f27f..1a06607 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,12 +3,14 @@ import 'package:enaklo_pos/core/constants/theme.dart'; import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; +import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_form/customer_form_bloc.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_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/product_loader/product_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_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/payment_form/payment_form_bloc.dart'; @@ -260,6 +262,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => RefundBloc(OrderRemoteDatasource()), ), + BlocProvider( + create: (context) => UserUpdateOutletBloc(UserRemoteDatasource()), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart new file mode 100644 index 0000000..7c9ae26 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart @@ -0,0 +1,32 @@ +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_update_outlet_event.dart'; +part 'user_update_outlet_state.dart'; +part 'user_update_outlet_bloc.freezed.dart'; + +class UserUpdateOutletBloc + extends Bloc { + final UserRemoteDatasource _userRemoteDatasource; + + UserUpdateOutletBloc(this._userRemoteDatasource) + : super(UserUpdateOutletState.initial()) { + on<_Update>( + (event, emit) async { + emit(const _Loading()); + + final result = await _userRemoteDatasource.updateOutlet( + event.outlet, + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success()), + ); + }, + ); + } +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart new file mode 100644 index 0000000..75d6ad0 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart @@ -0,0 +1,811 @@ +// 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 'user_update_outlet_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$UserUpdateOutletEvent { + String get outlet => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String outlet) update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String outlet)? update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String outlet)? update, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Update value) update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Update value)? update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Update value)? update, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserUpdateOutletEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserUpdateOutletEventCopyWith<$Res> { + factory $UserUpdateOutletEventCopyWith(UserUpdateOutletEvent value, + $Res Function(UserUpdateOutletEvent) then) = + _$UserUpdateOutletEventCopyWithImpl<$Res, UserUpdateOutletEvent>; + @useResult + $Res call({String outlet}); +} + +/// @nodoc +class _$UserUpdateOutletEventCopyWithImpl<$Res, + $Val extends UserUpdateOutletEvent> + implements $UserUpdateOutletEventCopyWith<$Res> { + _$UserUpdateOutletEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlet = null, + }) { + return _then(_value.copyWith( + outlet: null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$UpdateImplCopyWith<$Res> + implements $UserUpdateOutletEventCopyWith<$Res> { + factory _$$UpdateImplCopyWith( + _$UpdateImpl value, $Res Function(_$UpdateImpl) then) = + __$$UpdateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String outlet}); +} + +/// @nodoc +class __$$UpdateImplCopyWithImpl<$Res> + extends _$UserUpdateOutletEventCopyWithImpl<$Res, _$UpdateImpl> + implements _$$UpdateImplCopyWith<$Res> { + __$$UpdateImplCopyWithImpl( + _$UpdateImpl _value, $Res Function(_$UpdateImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlet = null, + }) { + return _then(_$UpdateImpl( + null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$UpdateImpl implements _Update { + const _$UpdateImpl(this.outlet); + + @override + final String outlet; + + @override + String toString() { + return 'UserUpdateOutletEvent.update(outlet: $outlet)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateImpl && + (identical(other.outlet, outlet) || other.outlet == outlet)); + } + + @override + int get hashCode => Object.hash(runtimeType, outlet); + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateImplCopyWith<_$UpdateImpl> get copyWith => + __$$UpdateImplCopyWithImpl<_$UpdateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String outlet) update, + }) { + return update(outlet); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String outlet)? update, + }) { + return update?.call(outlet); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String outlet)? update, + required TResult orElse(), + }) { + if (update != null) { + return update(outlet); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Update value) update, + }) { + return update(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Update value)? update, + }) { + return update?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Update value)? update, + required TResult orElse(), + }) { + if (update != null) { + return update(this); + } + return orElse(); + } +} + +abstract class _Update implements UserUpdateOutletEvent { + const factory _Update(final String outlet) = _$UpdateImpl; + + @override + String get outlet; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateImplCopyWith<_$UpdateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$UserUpdateOutletState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $UserUpdateOutletStateCopyWith<$Res> { + factory $UserUpdateOutletStateCopyWith(UserUpdateOutletState value, + $Res Function(UserUpdateOutletState) then) = + _$UserUpdateOutletStateCopyWithImpl<$Res, UserUpdateOutletState>; +} + +/// @nodoc +class _$UserUpdateOutletStateCopyWithImpl<$Res, + $Val extends UserUpdateOutletState> + implements $UserUpdateOutletStateCopyWith<$Res> { + _$UserUpdateOutletStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserUpdateOutletState + /// 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 _$UserUpdateOutletStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.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({ + 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? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + 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({ + 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? 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 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 UserUpdateOutletState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$UserUpdateOutletStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.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({ + 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? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + 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({ + 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? 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 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 UserUpdateOutletState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$UserUpdateOutletStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.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({ + 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? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + 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({ + 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? 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 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 UserUpdateOutletState { + 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 _$UserUpdateOutletStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// 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 'UserUpdateOutletState.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 UserUpdateOutletState + /// 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({ + 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? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + 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({ + 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? 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 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 UserUpdateOutletState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart new file mode 100644 index 0000000..afccd81 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart @@ -0,0 +1,6 @@ +part of 'user_update_outlet_bloc.dart'; + +@freezed +class UserUpdateOutletEvent with _$UserUpdateOutletEvent { + const factory UserUpdateOutletEvent.update(String outlet) = _Update; +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart new file mode 100644 index 0000000..1eadb6f --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart @@ -0,0 +1,9 @@ +part of 'user_update_outlet_bloc.dart'; + +@freezed +class UserUpdateOutletState with _$UserUpdateOutletState { + const factory UserUpdateOutletState.initial() = _Initial; + const factory UserUpdateOutletState.loading() = _Loading; + const factory UserUpdateOutletState.success() = _Success; + const factory UserUpdateOutletState.error(String message) = _Error; +} diff --git a/lib/presentation/home/dialog/outlet_dialog.dart b/lib/presentation/home/dialog/outlet_dialog.dart index 46136a9..227fd19 100644 --- a/lib/presentation/home/dialog/outlet_dialog.dart +++ b/lib/presentation/home/dialog/outlet_dialog.dart @@ -1,6 +1,10 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; import 'package:enaklo_pos/presentation/home/widgets/outlet_card.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,6 +17,19 @@ class OutletDialog extends StatefulWidget { } class _OutletDialogState extends State { + Outlet? selectedOutlet; + + void selectOutlet(Outlet outlet) { + setState(() { + if (selectedOutlet == outlet) { + selectedOutlet = null; // Deselect jika outlet yang sama diklik + } else { + selectedOutlet = + outlet; // Select outlet baru (akan mengganti selection sebelumnya) + } + }); + } + @override void initState() { super.initState(); @@ -38,12 +55,40 @@ class _OutletDialogState extends State { child: Text(message), ), loaded: (outlets) => Column( - children: List.generate( - outlets.length, - (index) => OutletCard( - outlet: outlets[index], + children: [ + ...List.generate( + outlets.length, + (index) => GestureDetector( + onTap: () { + selectOutlet(outlets[index]); + }, + child: OutletCard( + outlet: outlets[index], + isSelected: selectedOutlet == outlets[index], + ), + ), ), - ), + SpaceHeight(24), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: selectedOutlet == null + ? null + : () { + context.read().add( + UserUpdateOutletEvent.update( + selectedOutlet!.id ?? "")); + }, + label: 'Terapkan', + ), + loading: () => Center( + child: CircularProgressIndicator(), + ), + ); + }, + ), + ], ), ); }, diff --git a/lib/presentation/home/pages/home_page.dart b/lib/presentation/home/pages/home_page.dart index f8f8c6c..e42a147 100644 --- a/lib/presentation/home/pages/home_page.dart +++ b/lib/presentation/home/pages/home_page.dart @@ -2,6 +2,7 @@ import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -99,455 +100,477 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { - return Hero( - tag: 'confirmation_screen', - child: Scaffold( - backgroundColor: AppColors.white, - body: Row( - children: [ - Expanded( - flex: 3, - child: Align( - alignment: AlignmentDirectional.topStart, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - HomeTitle( - controller: searchController, - onChanged: (value) { - setState(() { - searchQuery = value; - }); - }, - ), - BlocBuilder( - builder: (context, state) { - return Expanded( - child: CustomTabBarV2( - tabTitles: const [ - 'Semua', - 'Makanan', - 'Minuman', - 'Paket' - ], - tabViews: [ - // All Products Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - final filteredProducts = - _filterProducts(products); - if (filteredProducts.isEmpty) { - return const Center( - child: Text('No Items Found'), - ); - } - return GridView.builder( - itemCount: filteredProducts.length, - padding: const EdgeInsets.all(16), - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 180, - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 180 / 240, - ), - itemBuilder: (context, index) => - ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ), - ); - }), - ), - // Makanan Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = - _filterProductsByCategory(products, 1); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - itemCount: filteredProducts.length, - padding: const EdgeInsets.all(16), - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 0.85, - ), - itemBuilder: (context, index) => - ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ), - ); - }), - ), - // Minuman Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = - _filterProductsByCategory(products, 2); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - itemCount: filteredProducts.length, - padding: const EdgeInsets.all(16), - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 0.85, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), - ), - // Snack Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = - _filterProductsByCategory(products, 3); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - itemCount: filteredProducts.length, - padding: const EdgeInsets.all(16), - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 0.85, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), - ), - ], - ), - ); - }, - ), - ], - ), - ), - ), - Expanded( - flex: 2, - child: Align( - alignment: Alignment.topCenter, - child: Material( - color: Colors.white, + return BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + loading: () {}, + success: () { + context.pop(); + Future.delayed(Duration(milliseconds: 300), () { + AppFlushbar.showSuccess(context, 'Outlet berhasil diubah'); + context + .read() + .add(CurrentOutletEvent.currentOutlet()); + }); + }, + error: (message) => AppFlushbar.showError(context, message), + ); + }, + child: Hero( + tag: 'confirmation_screen', + child: Scaffold( + backgroundColor: AppColors.white, + body: Row( + children: [ + Expanded( + flex: 3, + child: Align( + alignment: AlignmentDirectional.topStart, child: Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ - HomeRightTitle( - table: widget.table, + HomeTitle( + controller: searchController, + onChanged: (value) { + setState(() { + searchQuery = value; + }); + }, ), - Padding( - padding: const EdgeInsets.all(16.0) - .copyWith(bottom: 0, top: 27), - child: Column( - children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Item', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - SizedBox( - width: 130, - ), - SizedBox( - width: 50.0, - child: Text( - 'Qty', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - SizedBox( - child: Text( - 'Price', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), + BlocBuilder( + builder: (context, state) { + return Expanded( + child: CustomTabBarV2( + tabTitles: const [ + 'Semua', + 'Makanan', + 'Minuman', + 'Paket' ], - ), - const SpaceHeight(8), - const Divider(), - ], - ), - ), - Expanded( - child: BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: Text('No Items'), - ), - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - return ListView.separated( - shrinkWrap: true, - padding: const EdgeInsets.symmetric( - horizontal: 16), - itemBuilder: (context, index) => - OrderMenu(data: products[index]), - separatorBuilder: (context, index) => - const SpaceHeight(1.0), - itemCount: products.length, - ); - }, - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(16.0).copyWith(top: 0), - child: Column( - children: [ - const Divider(), - const SpaceHeight(16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Pajak', - style: TextStyle( - color: AppColors.black, - fontWeight: FontWeight.bold, - ), - ), - BlocBuilder( - builder: (context, state) { - final tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - if (products.isEmpty) { - return 0; - } - return tax; - }); - return Text( - '$tax %', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, + tabViews: [ + // All Products Tab + SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loaded: (products) { + final filteredProducts = + _filterProducts(products); + if (filteredProducts.isEmpty) { + return const Center( + child: Text('No Items Found'), + ); + } + return GridView.builder( + itemCount: filteredProducts.length, + padding: const EdgeInsets.all(16), + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 180, + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 180 / 240, + ), + itemBuilder: (context, index) => + ProductCard( + data: filteredProducts[index], + onCartButton: () {}, ), ); - }, + }), ), - ], - ), - const SpaceHeight(16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Sub total', - style: TextStyle( - color: AppColors.black, - fontWeight: FontWeight.bold, - ), - ), - BlocBuilder( - builder: (context, state) { - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - if (products.isEmpty) { - return 0; - } - return products - .map((e) => - e.product.price! * e.quantity) - .reduce((value, element) => - value + element); - }); - - return Text( - price.currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w900, - ), + // Makanan Tab + SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), ); - }, + }, loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loaded: (products) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + final filteredProducts = + _filterProductsByCategory(products, 1); + return filteredProducts.isEmpty + ? const _IsEmpty() + : GridView.builder( + itemCount: filteredProducts.length, + padding: const EdgeInsets.all(16), + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: + 200, // Lebar maksimal tiap item (bisa kamu ubah) + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 0.85, + ), + itemBuilder: (context, index) => + ProductCard( + data: filteredProducts[index], + onCartButton: () {}, + ), + ); + }), + ), + // Minuman Tab + SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loaded: (products) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + final filteredProducts = + _filterProductsByCategory(products, 2); + return filteredProducts.isEmpty + ? const _IsEmpty() + : GridView.builder( + itemCount: filteredProducts.length, + padding: const EdgeInsets.all(16), + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: + 200, // Lebar maksimal tiap item (bisa kamu ubah) + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 0.85, + ), + itemBuilder: (context, index) { + return ProductCard( + data: filteredProducts[index], + onCartButton: () {}, + ); + }, + ); + }), + ), + // Snack Tab + SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, loaded: (products) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + final filteredProducts = + _filterProductsByCategory(products, 3); + return filteredProducts.isEmpty + ? const _IsEmpty() + : GridView.builder( + itemCount: filteredProducts.length, + padding: const EdgeInsets.all(16), + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: + 200, // Lebar maksimal tiap item (bisa kamu ubah) + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 0.85, + ), + itemBuilder: (context, index) { + return ProductCard( + data: filteredProducts[index], + onCartButton: () {}, + ); + }, + ); + }), ), ], ), - SpaceHeight(16.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => Align( - alignment: Alignment.bottomCenter, - child: Button.filled( - borderRadius: 12, - elevation: 1, - disabled: true, - onPressed: () { - context.push(ConfirmPaymentPage( - isTable: widget.table == null - ? false - : true, - table: widget.table, - )); - }, - label: 'Lanjutkan Pembayaran', - ), - ), - loaded: (items, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - Align( - alignment: Alignment.bottomCenter, - child: Button.filled( - borderRadius: 12, - elevation: 1, - disabled: items.isEmpty, - onPressed: () { - if (orderType.name == 'dineIn' && - widget.table == null) { - AppFlushbar.showError(context, - 'Mohon pilih meja terlebih dahulu'); - return; - } - context.push(ConfirmPaymentPage( - isTable: widget.table == null - ? false - : true, - table: widget.table, - )); - }, - label: 'Lanjutkan Pembayaran', - ), - ), - ); - }, - ), - ], - ), + ); + }, ), ], ), ), ), - ), - ], + Expanded( + flex: 2, + child: Align( + alignment: Alignment.topCenter, + child: Material( + color: Colors.white, + child: Column( + children: [ + HomeRightTitle( + table: widget.table, + ), + Padding( + padding: const EdgeInsets.all(16.0) + .copyWith(bottom: 0, top: 27), + child: Column( + children: [ + const Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Item', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + SizedBox( + width: 130, + ), + SizedBox( + width: 50.0, + child: Text( + 'Qty', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox( + child: Text( + 'Price', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + const SpaceHeight(8), + const Divider(), + ], + ), + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: Text('No Items'), + ), + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + return ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: 16), + itemBuilder: (context, index) => + OrderMenu(data: products[index]), + separatorBuilder: (context, index) => + const SpaceHeight(1.0), + itemCount: products.length, + ); + }, + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0).copyWith(top: 0), + child: Column( + children: [ + const Divider(), + const SpaceHeight(16.0), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Pajak', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + ), + ), + BlocBuilder( + builder: (context, state) { + final tax = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) { + if (products.isEmpty) { + return 0; + } + return tax; + }); + return Text( + '$tax %', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(16.0), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Sub total', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + ), + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) { + if (products.isEmpty) { + return 0; + } + return products + .map((e) => + e.product.price! * + e.quantity) + .reduce((value, element) => + value + element); + }); + + return Text( + price.currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w900, + ), + ); + }, + ), + ], + ), + SpaceHeight(16.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Align( + alignment: Alignment.bottomCenter, + child: Button.filled( + borderRadius: 12, + elevation: 1, + disabled: true, + onPressed: () { + context.push(ConfirmPaymentPage( + isTable: widget.table == null + ? false + : true, + table: widget.table, + )); + }, + label: 'Lanjutkan Pembayaran', + ), + ), + loaded: (items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + Align( + alignment: Alignment.bottomCenter, + child: Button.filled( + borderRadius: 12, + elevation: 1, + disabled: items.isEmpty, + onPressed: () { + if (orderType.name == 'dineIn' && + widget.table == null) { + AppFlushbar.showError(context, + 'Mohon pilih meja terlebih dahulu'); + return; + } + context.push(ConfirmPaymentPage( + isTable: widget.table == null + ? false + : true, + table: widget.table, + )); + }, + label: 'Lanjutkan Pembayaran', + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ), ), ), ); diff --git a/lib/presentation/home/widgets/outlet_card.dart b/lib/presentation/home/widgets/outlet_card.dart index cec6f02..8bd44a9 100644 --- a/lib/presentation/home/widgets/outlet_card.dart +++ b/lib/presentation/home/widgets/outlet_card.dart @@ -5,7 +5,8 @@ import 'package:flutter/material.dart'; class OutletCard extends StatelessWidget { final Outlet outlet; - const OutletCard({super.key, required this.outlet}); + final bool isSelected; + const OutletCard({super.key, required this.outlet, required this.isSelected}); @override Widget build(BuildContext context) { @@ -16,7 +17,8 @@ class OutletCard extends StatelessWidget { ), margin: EdgeInsets.only(bottom: 12), decoration: BoxDecoration( - color: AppColors.white, + color: + isSelected ? AppColors.primary.withOpacity(0.1) : AppColors.white, border: Border.all(color: AppColors.primary), borderRadius: const BorderRadius.all(Radius.circular(8.0)), ),