diff --git a/lib/application/outlet/selected_outlet/selected_outlet_bloc.dart b/lib/application/outlet/selected_outlet/selected_outlet_bloc.dart new file mode 100644 index 0000000..77682f3 --- /dev/null +++ b/lib/application/outlet/selected_outlet/selected_outlet_bloc.dart @@ -0,0 +1,46 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/outlet/outlet.dart'; +import '../../../infrastructure/outlet/datasource/local_data_provider.dart'; + +part 'selected_outlet_event.dart'; +part 'selected_outlet_state.dart'; +part 'selected_outlet_bloc.freezed.dart'; + +@injectable +class SelectedOutletBloc + extends Bloc { + final OutletLocalDataProvider _localDataProvider; + + SelectedOutletBloc(this._localDataProvider) + : super(SelectedOutletState.initial()) { + on(_onSelectedOutletEvent); + } + + Future _onSelectedOutletEvent( + SelectedOutletEvent event, + Emitter emit, + ) { + return event.map( + loaded: (e) async { + final savedId = _localDataProvider.getSelectedOutletId(); + emit(state.copyWith(selectedOutletId: savedId)); + }, + selected: (e) async { + await _localDataProvider.saveSelectedOutletId(e.outlet.id); + emit( + state.copyWith( + selectedOutlet: e.outlet, + selectedOutletId: e.outlet.id, + ), + ); + }, + cleared: (e) async { + await _localDataProvider.deleteSelectedOutletId(); + emit(SelectedOutletState.initial()); + }, + ); + } +} diff --git a/lib/application/outlet/selected_outlet/selected_outlet_bloc.freezed.dart b/lib/application/outlet/selected_outlet/selected_outlet_bloc.freezed.dart new file mode 100644 index 0000000..634f59d --- /dev/null +++ b/lib/application/outlet/selected_outlet/selected_outlet_bloc.freezed.dart @@ -0,0 +1,649 @@ +// 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 'selected_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 _$SelectedOutletEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() loaded, + required TResult Function(Outlet outlet) selected, + required TResult Function() cleared, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loaded, + TResult? Function(Outlet outlet)? selected, + TResult? Function()? cleared, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loaded, + TResult Function(Outlet outlet)? selected, + TResult Function()? cleared, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Loaded value) loaded, + required TResult Function(_Selected value) selected, + required TResult Function(_Cleared value) cleared, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Selected value)? selected, + TResult? Function(_Cleared value)? cleared, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Loaded value)? loaded, + TResult Function(_Selected value)? selected, + TResult Function(_Cleared value)? cleared, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SelectedOutletEventCopyWith<$Res> { + factory $SelectedOutletEventCopyWith( + SelectedOutletEvent value, + $Res Function(SelectedOutletEvent) then, + ) = _$SelectedOutletEventCopyWithImpl<$Res, SelectedOutletEvent>; +} + +/// @nodoc +class _$SelectedOutletEventCopyWithImpl<$Res, $Val extends SelectedOutletEvent> + implements $SelectedOutletEventCopyWith<$Res> { + _$SelectedOutletEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, + $Res Function(_$LoadedImpl) then, + ) = __$$LoadedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$SelectedOutletEventCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, + $Res Function(_$LoadedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(); + + @override + String toString() { + return 'SelectedOutletEvent.loaded()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loaded, + required TResult Function(Outlet outlet) selected, + required TResult Function() cleared, + }) { + return loaded(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loaded, + TResult? Function(Outlet outlet)? selected, + TResult? Function()? cleared, + }) { + return loaded?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loaded, + TResult Function(Outlet outlet)? selected, + TResult Function()? cleared, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Loaded value) loaded, + required TResult Function(_Selected value) selected, + required TResult Function(_Cleared value) cleared, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Selected value)? selected, + TResult? Function(_Cleared value)? cleared, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Loaded value)? loaded, + TResult Function(_Selected value)? selected, + TResult Function(_Cleared value)? cleared, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _Loaded implements SelectedOutletEvent { + const factory _Loaded() = _$LoadedImpl; +} + +/// @nodoc +abstract class _$$SelectedImplCopyWith<$Res> { + factory _$$SelectedImplCopyWith( + _$SelectedImpl value, + $Res Function(_$SelectedImpl) then, + ) = __$$SelectedImplCopyWithImpl<$Res>; + @useResult + $Res call({Outlet outlet}); + + $OutletCopyWith<$Res> get outlet; +} + +/// @nodoc +class __$$SelectedImplCopyWithImpl<$Res> + extends _$SelectedOutletEventCopyWithImpl<$Res, _$SelectedImpl> + implements _$$SelectedImplCopyWith<$Res> { + __$$SelectedImplCopyWithImpl( + _$SelectedImpl _value, + $Res Function(_$SelectedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? outlet = null}) { + return _then( + _$SelectedImpl( + null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as Outlet, + ), + ); + } + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OutletCopyWith<$Res> get outlet { + return $OutletCopyWith<$Res>(_value.outlet, (value) { + return _then(_value.copyWith(outlet: value)); + }); + } +} + +/// @nodoc + +class _$SelectedImpl implements _Selected { + const _$SelectedImpl(this.outlet); + + @override + final Outlet outlet; + + @override + String toString() { + return 'SelectedOutletEvent.selected(outlet: $outlet)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SelectedImpl && + (identical(other.outlet, outlet) || other.outlet == outlet)); + } + + @override + int get hashCode => Object.hash(runtimeType, outlet); + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SelectedImplCopyWith<_$SelectedImpl> get copyWith => + __$$SelectedImplCopyWithImpl<_$SelectedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loaded, + required TResult Function(Outlet outlet) selected, + required TResult Function() cleared, + }) { + return selected(outlet); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loaded, + TResult? Function(Outlet outlet)? selected, + TResult? Function()? cleared, + }) { + return selected?.call(outlet); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loaded, + TResult Function(Outlet outlet)? selected, + TResult Function()? cleared, + required TResult orElse(), + }) { + if (selected != null) { + return selected(outlet); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Loaded value) loaded, + required TResult Function(_Selected value) selected, + required TResult Function(_Cleared value) cleared, + }) { + return selected(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Selected value)? selected, + TResult? Function(_Cleared value)? cleared, + }) { + return selected?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Loaded value)? loaded, + TResult Function(_Selected value)? selected, + TResult Function(_Cleared value)? cleared, + required TResult orElse(), + }) { + if (selected != null) { + return selected(this); + } + return orElse(); + } +} + +abstract class _Selected implements SelectedOutletEvent { + const factory _Selected(final Outlet outlet) = _$SelectedImpl; + + Outlet get outlet; + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SelectedImplCopyWith<_$SelectedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ClearedImplCopyWith<$Res> { + factory _$$ClearedImplCopyWith( + _$ClearedImpl value, + $Res Function(_$ClearedImpl) then, + ) = __$$ClearedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ClearedImplCopyWithImpl<$Res> + extends _$SelectedOutletEventCopyWithImpl<$Res, _$ClearedImpl> + implements _$$ClearedImplCopyWith<$Res> { + __$$ClearedImplCopyWithImpl( + _$ClearedImpl _value, + $Res Function(_$ClearedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SelectedOutletEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ClearedImpl implements _Cleared { + const _$ClearedImpl(); + + @override + String toString() { + return 'SelectedOutletEvent.cleared()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ClearedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loaded, + required TResult Function(Outlet outlet) selected, + required TResult Function() cleared, + }) { + return cleared(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loaded, + TResult? Function(Outlet outlet)? selected, + TResult? Function()? cleared, + }) { + return cleared?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loaded, + TResult Function(Outlet outlet)? selected, + TResult Function()? cleared, + required TResult orElse(), + }) { + if (cleared != null) { + return cleared(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Loaded value) loaded, + required TResult Function(_Selected value) selected, + required TResult Function(_Cleared value) cleared, + }) { + return cleared(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Selected value)? selected, + TResult? Function(_Cleared value)? cleared, + }) { + return cleared?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Loaded value)? loaded, + TResult Function(_Selected value)? selected, + TResult Function(_Cleared value)? cleared, + required TResult orElse(), + }) { + if (cleared != null) { + return cleared(this); + } + return orElse(); + } +} + +abstract class _Cleared implements SelectedOutletEvent { + const factory _Cleared() = _$ClearedImpl; +} + +/// @nodoc +mixin _$SelectedOutletState { + /// null berarti "Semua Outlet" + Outlet? get selectedOutlet => throw _privateConstructorUsedError; + String? get selectedOutletId => throw _privateConstructorUsedError; + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SelectedOutletStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SelectedOutletStateCopyWith<$Res> { + factory $SelectedOutletStateCopyWith( + SelectedOutletState value, + $Res Function(SelectedOutletState) then, + ) = _$SelectedOutletStateCopyWithImpl<$Res, SelectedOutletState>; + @useResult + $Res call({Outlet? selectedOutlet, String? selectedOutletId}); + + $OutletCopyWith<$Res>? get selectedOutlet; +} + +/// @nodoc +class _$SelectedOutletStateCopyWithImpl<$Res, $Val extends SelectedOutletState> + implements $SelectedOutletStateCopyWith<$Res> { + _$SelectedOutletStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? selectedOutlet = freezed, + Object? selectedOutletId = freezed, + }) { + return _then( + _value.copyWith( + selectedOutlet: freezed == selectedOutlet + ? _value.selectedOutlet + : selectedOutlet // ignore: cast_nullable_to_non_nullable + as Outlet?, + selectedOutletId: freezed == selectedOutletId + ? _value.selectedOutletId + : selectedOutletId // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OutletCopyWith<$Res>? get selectedOutlet { + if (_value.selectedOutlet == null) { + return null; + } + + return $OutletCopyWith<$Res>(_value.selectedOutlet!, (value) { + return _then(_value.copyWith(selectedOutlet: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SelectedOutletStateImplCopyWith<$Res> + implements $SelectedOutletStateCopyWith<$Res> { + factory _$$SelectedOutletStateImplCopyWith( + _$SelectedOutletStateImpl value, + $Res Function(_$SelectedOutletStateImpl) then, + ) = __$$SelectedOutletStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Outlet? selectedOutlet, String? selectedOutletId}); + + @override + $OutletCopyWith<$Res>? get selectedOutlet; +} + +/// @nodoc +class __$$SelectedOutletStateImplCopyWithImpl<$Res> + extends _$SelectedOutletStateCopyWithImpl<$Res, _$SelectedOutletStateImpl> + implements _$$SelectedOutletStateImplCopyWith<$Res> { + __$$SelectedOutletStateImplCopyWithImpl( + _$SelectedOutletStateImpl _value, + $Res Function(_$SelectedOutletStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? selectedOutlet = freezed, + Object? selectedOutletId = freezed, + }) { + return _then( + _$SelectedOutletStateImpl( + selectedOutlet: freezed == selectedOutlet + ? _value.selectedOutlet + : selectedOutlet // ignore: cast_nullable_to_non_nullable + as Outlet?, + selectedOutletId: freezed == selectedOutletId + ? _value.selectedOutletId + : selectedOutletId // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$SelectedOutletStateImpl implements _SelectedOutletState { + const _$SelectedOutletStateImpl({this.selectedOutlet, this.selectedOutletId}); + + /// null berarti "Semua Outlet" + @override + final Outlet? selectedOutlet; + @override + final String? selectedOutletId; + + @override + String toString() { + return 'SelectedOutletState(selectedOutlet: $selectedOutlet, selectedOutletId: $selectedOutletId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SelectedOutletStateImpl && + (identical(other.selectedOutlet, selectedOutlet) || + other.selectedOutlet == selectedOutlet) && + (identical(other.selectedOutletId, selectedOutletId) || + other.selectedOutletId == selectedOutletId)); + } + + @override + int get hashCode => + Object.hash(runtimeType, selectedOutlet, selectedOutletId); + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SelectedOutletStateImplCopyWith<_$SelectedOutletStateImpl> get copyWith => + __$$SelectedOutletStateImplCopyWithImpl<_$SelectedOutletStateImpl>( + this, + _$identity, + ); +} + +abstract class _SelectedOutletState implements SelectedOutletState { + const factory _SelectedOutletState({ + final Outlet? selectedOutlet, + final String? selectedOutletId, + }) = _$SelectedOutletStateImpl; + + /// null berarti "Semua Outlet" + @override + Outlet? get selectedOutlet; + @override + String? get selectedOutletId; + + /// Create a copy of SelectedOutletState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SelectedOutletStateImplCopyWith<_$SelectedOutletStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/outlet/selected_outlet/selected_outlet_event.dart b/lib/application/outlet/selected_outlet/selected_outlet_event.dart new file mode 100644 index 0000000..ddfd6b8 --- /dev/null +++ b/lib/application/outlet/selected_outlet/selected_outlet_event.dart @@ -0,0 +1,13 @@ +part of 'selected_outlet_bloc.dart'; + +@freezed +class SelectedOutletEvent with _$SelectedOutletEvent { + /// Load selected outlet id dari shared preferences + const factory SelectedOutletEvent.loaded() = _Loaded; + + /// User memilih outlet tertentu + const factory SelectedOutletEvent.selected(Outlet outlet) = _Selected; + + /// Reset ke "Semua Outlet" + const factory SelectedOutletEvent.cleared() = _Cleared; +} diff --git a/lib/application/outlet/selected_outlet/selected_outlet_state.dart b/lib/application/outlet/selected_outlet/selected_outlet_state.dart new file mode 100644 index 0000000..3cbf827 --- /dev/null +++ b/lib/application/outlet/selected_outlet/selected_outlet_state.dart @@ -0,0 +1,18 @@ +part of 'selected_outlet_bloc.dart'; + +@freezed +class SelectedOutletState with _$SelectedOutletState { + const factory SelectedOutletState({ + /// null berarti "Semua Outlet" + Outlet? selectedOutlet, + String? selectedOutletId, + }) = _SelectedOutletState; + + factory SelectedOutletState.initial() => const SelectedOutletState(); +} + +extension SelectedOutletStateX on SelectedOutletState { + bool get isAllOutlets => selectedOutletId == null; + + String get displayName => selectedOutlet?.name ?? 'Semua Outlet'; +} diff --git a/lib/common/constant/local_storage_key.dart b/lib/common/constant/local_storage_key.dart index e8a67f6..06f4f98 100644 --- a/lib/common/constant/local_storage_key.dart +++ b/lib/common/constant/local_storage_key.dart @@ -2,4 +2,5 @@ class LocalStorageKey { static const String lang = 'lang'; static const String token = 'token'; static const String user = 'user'; + static const String selectedOutletId = 'selected_outlet_id'; } diff --git a/lib/domain/analytic/repositories/i_analytic_repository.dart b/lib/domain/analytic/repositories/i_analytic_repository.dart index 60fb168..dbaf131 100644 --- a/lib/domain/analytic/repositories/i_analytic_repository.dart +++ b/lib/domain/analytic/repositories/i_analytic_repository.dart @@ -6,35 +6,42 @@ abstract class IAnalyticRepository { Future> getSales({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getProfitLoss({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getCategory({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getInventory({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getDashboard({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getProduct({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); Future> getPaymentMethod({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }); } diff --git a/lib/domain/order/repositories/i_order_repository.dart b/lib/domain/order/repositories/i_order_repository.dart index 8e3d008..7f13301 100644 --- a/lib/domain/order/repositories/i_order_repository.dart +++ b/lib/domain/order/repositories/i_order_repository.dart @@ -6,6 +6,7 @@ abstract class IOrderRepository { int limit = 10, String? status, String? search, + String? outletId, required DateTime dateFrom, required DateTime dateTo, }); diff --git a/lib/infrastructure/analytic/datasource/remote_data_provider.dart b/lib/infrastructure/analytic/datasource/remote_data_provider.dart index ff52f3d..29cf105 100644 --- a/lib/infrastructure/analytic/datasource/remote_data_provider.dart +++ b/lib/infrastructure/analytic/datasource/remote_data_provider.dart @@ -21,14 +21,18 @@ class AnalyticRemoteDataProvider { Future> fetchSales({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.salesAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); @@ -48,14 +52,18 @@ class AnalyticRemoteDataProvider { Future> fetchProfitLoss({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.profitLossAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); @@ -75,14 +83,18 @@ class AnalyticRemoteDataProvider { Future> fetchCategory({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.categoryAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); @@ -128,17 +140,20 @@ class AnalyticRemoteDataProvider { } Future> fetchDashboard({ - required String outletId, required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.dashboardAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); @@ -156,17 +171,20 @@ class AnalyticRemoteDataProvider { } Future> fetchProduct({ - required String outletId, required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.productAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); @@ -184,17 +202,20 @@ class AnalyticRemoteDataProvider { } Future> fetchPaymentMethod({ - required String outletId, required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { + final Map params = { + 'date_from': dateFrom.toServerDate, + 'date_to': dateTo.toServerDate, + }; + if (outletId != null) params['outlet_id'] = outletId; + final response = await _apiClient.get( ApiPath.paymentMethodAnalytic, - params: { - 'date_from': dateFrom.toServerDate, - 'date_to': dateTo.toServerDate, - }, + params: params, headers: getAuthorizationHeader(), ); diff --git a/lib/infrastructure/analytic/repositories/analytic_repository.dart b/lib/infrastructure/analytic/repositories/analytic_repository.dart index 66c4dda..c32832e 100644 --- a/lib/infrastructure/analytic/repositories/analytic_repository.dart +++ b/lib/infrastructure/analytic/repositories/analytic_repository.dart @@ -5,36 +5,43 @@ import 'package:injectable/injectable.dart'; import '../../../domain/analytic/analytic.dart'; import '../../../domain/analytic/repositories/i_analytic_repository.dart'; -import '../../../domain/user/user.dart'; import '../../auth/datasources/local_data_provider.dart'; +import '../../outlet/datasource/local_data_provider.dart'; import '../datasource/remote_data_provider.dart'; @Injectable(as: IAnalyticRepository) class AnalyticRepository implements IAnalyticRepository { final AnalyticRemoteDataProvider _dataProvider; final AuthLocalDataProvider _authLocalDataProvider; + final OutletLocalDataProvider _outletLocalDataProvider; final String _logName = 'AnalyticRepository'; - AnalyticRepository(this._dataProvider, this._authLocalDataProvider); + AnalyticRepository( + this._dataProvider, + this._authLocalDataProvider, + this._outletLocalDataProvider, + ); + + /// Resolves outlet_id: pakai param jika ada, fallback ke shared pref + String? _resolveOutletId(String? outletId) { + return outletId ?? _outletLocalDataProvider.getSelectedOutletId(); + } @override Future> getSales({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { final result = await _dataProvider.fetchSales( dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getSalesError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -45,20 +52,17 @@ class AnalyticRepository implements IAnalyticRepository { Future> getProfitLoss({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { final result = await _dataProvider.fetchProfitLoss( dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getProfitLossError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -69,20 +73,17 @@ class AnalyticRepository implements IAnalyticRepository { Future> getCategory({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { final result = await _dataProvider.fetchCategory( dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getCategoryError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -93,23 +94,20 @@ class AnalyticRepository implements IAnalyticRepository { Future> getInventory({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { - User currentUser = await _authLocalDataProvider.currentUser(); + final currentUser = await _authLocalDataProvider.currentUser(); + final resolvedId = _resolveOutletId(outletId) ?? currentUser.outletId; final result = await _dataProvider.fetchInventory( - outletId: currentUser.outletId, + outletId: resolvedId, dateFrom: dateFrom, dateTo: dateTo, ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getInventoryError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -120,23 +118,17 @@ class AnalyticRepository implements IAnalyticRepository { Future> getDashboard({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { - User currentUser = await _authLocalDataProvider.currentUser(); - final result = await _dataProvider.fetchDashboard( - outletId: currentUser.outletId, dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getDashboardError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -147,23 +139,17 @@ class AnalyticRepository implements IAnalyticRepository { Future> getProduct({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { - User currentUser = await _authLocalDataProvider.currentUser(); - final result = await _dataProvider.fetchProduct( - outletId: currentUser.outletId, dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getProductError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); @@ -174,23 +160,17 @@ class AnalyticRepository implements IAnalyticRepository { Future> getPaymentMethod({ required DateTime dateFrom, required DateTime dateTo, + String? outletId, }) async { try { - User currentUser = await _authLocalDataProvider.currentUser(); - final result = await _dataProvider.fetchPaymentMethod( - outletId: currentUser.outletId, dateFrom: dateFrom, dateTo: dateTo, + outletId: _resolveOutletId(outletId), ); - if (result.hasError) { - return left(result.error!); - } - - final auth = result.data!.toDomain(); - - return right(auth); + if (result.hasError) return left(result.error!); + return right(result.data!.toDomain()); } catch (e, s) { log('getPaymentMethodError', name: _logName, error: e, stackTrace: s); return left(const AnalyticFailure.unexpectedError()); diff --git a/lib/infrastructure/order/datasource/remote_data_provider.dart b/lib/infrastructure/order/datasource/remote_data_provider.dart index 834ac05..ecc4fda 100644 --- a/lib/infrastructure/order/datasource/remote_data_provider.dart +++ b/lib/infrastructure/order/datasource/remote_data_provider.dart @@ -23,6 +23,7 @@ class OrderRemoteDataProvider { int limit = 10, String? status, String? search, + String? outletId, required DateTime dateFrom, required DateTime dateTo, }) async { @@ -42,6 +43,10 @@ class OrderRemoteDataProvider { params['search'] = search; } + // if (outletId != null) { + // params['outlet_id'] = outletId; + // } + final response = await _apiClient.get( ApiPath.order, params: params, diff --git a/lib/infrastructure/order/repositories/order_repository.dart b/lib/infrastructure/order/repositories/order_repository.dart index bac3e8c..6317fe8 100644 --- a/lib/infrastructure/order/repositories/order_repository.dart +++ b/lib/infrastructure/order/repositories/order_repository.dart @@ -4,14 +4,16 @@ import 'package:dartz/dartz.dart' hide Order; import 'package:injectable/injectable.dart' hide Order; import '../../../domain/order/order.dart'; +import '../../outlet/datasource/local_data_provider.dart'; import '../datasource/remote_data_provider.dart'; @Injectable(as: IOrderRepository) class OrderRepository implements IOrderRepository { final OrderRemoteDataProvider _dataProvider; + final OutletLocalDataProvider _outletLocalDataProvider; final String _logName = 'OrderRepository'; - OrderRepository(this._dataProvider); + OrderRepository(this._dataProvider, this._outletLocalDataProvider); @override Future>> get({ @@ -19,15 +21,20 @@ class OrderRepository implements IOrderRepository { int limit = 20, String? status, String? search, + String? outletId, required DateTime dateFrom, required DateTime dateTo, }) async { try { + final resolvedOutletId = + outletId ?? _outletLocalDataProvider.getSelectedOutletId(); + final result = await _dataProvider.fetch( page: page, limit: limit, status: status, search: search, + outletId: resolvedOutletId, dateFrom: dateFrom, dateTo: dateTo, ); @@ -36,9 +43,9 @@ class OrderRepository implements IOrderRepository { return left(result.error!); } - final auth = result.data!.map((e) => e.toDomain()).toList(); + final orders = result.data!.map((e) => e.toDomain()).toList(); - return right(auth); + return right(orders); } catch (e, s) { log('getOrderError', name: _logName, error: e, stackTrace: s); return left(const OrderFailure.unexpectedError()); diff --git a/lib/infrastructure/outlet/datasource/local_data_provider.dart b/lib/infrastructure/outlet/datasource/local_data_provider.dart new file mode 100644 index 0000000..8fe1b93 --- /dev/null +++ b/lib/infrastructure/outlet/datasource/local_data_provider.dart @@ -0,0 +1,26 @@ +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../common/constant/local_storage_key.dart'; + +@injectable +class OutletLocalDataProvider { + final SharedPreferences _sharedPreferences; + + OutletLocalDataProvider(this._sharedPreferences); + + Future saveSelectedOutletId(String outletId) async { + await _sharedPreferences.setString( + LocalStorageKey.selectedOutletId, + outletId, + ); + } + + String? getSelectedOutletId() { + return _sharedPreferences.getString(LocalStorageKey.selectedOutletId); + } + + Future deleteSelectedOutletId() async { + await _sharedPreferences.remove(LocalStorageKey.selectedOutletId); + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 6f2079d..2ca1bda 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -41,6 +41,8 @@ import 'package:apskel_owner_flutter/application/outlet/current_outlet_loader/cu as _i337; import 'package:apskel_owner_flutter/application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart' as _i877; +import 'package:apskel_owner_flutter/application/outlet/selected_outlet/selected_outlet_bloc.dart' + as _i678; import 'package:apskel_owner_flutter/application/product/product_loader/product_loader_bloc.dart' as _i458; import 'package:apskel_owner_flutter/application/report/inventory_report/inventory_report_bloc.dart' @@ -96,6 +98,8 @@ import 'package:apskel_owner_flutter/infrastructure/order/datasource/remote_data as _i130; import 'package:apskel_owner_flutter/infrastructure/order/repositories/order_repository.dart' as _i641; +import 'package:apskel_owner_flutter/infrastructure/outlet/datasource/local_data_provider.dart' + as _i850; import 'package:apskel_owner_flutter/infrastructure/outlet/datasource/remote_data_provider.dart' as _i27; import 'package:apskel_owner_flutter/infrastructure/outlet/repositories/outlet_repository.dart' @@ -161,6 +165,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i991.AuthLocalDataProvider>( () => _i991.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), ); + gh.factory<_i850.OutletLocalDataProvider>( + () => _i850.OutletLocalDataProvider(gh<_i460.SharedPreferences>()), + ); gh.lazySingleton<_i115.ApiClient>( () => _i115.ApiClient(gh<_i361.Dio>(), gh<_i6.Env>()), ); @@ -192,15 +199,15 @@ extension GetItInjectableX on _i174.GetIt { 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>(), gh<_i17.AuthRemoteDataProvider>(), ), ); + gh.factory<_i678.SelectedOutletBloc>( + () => _i678.SelectedOutletBloc(gh<_i850.OutletLocalDataProvider>()), + ); gh.factory<_i635.IUserRepository>( () => _i754.UserRepository( gh<_i785.UserRemoteDataProvider>(), @@ -210,15 +217,15 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i419.IProductRepository>( () => _i121.ProductRepository(gh<_i823.ProductRemoteDataProvider>()), ); + gh.factory<_i219.IOrderRepository>( + () => _i641.OrderRepository( + gh<_i130.OrderRemoteDataProvider>(), + gh<_i850.OutletLocalDataProvider>(), + ), + ); gh.factory<_i972.CustomerLoaderBloc>( () => _i972.CustomerLoaderBloc(gh<_i48.ICustomerRepository>()), ); - gh.factory<_i477.IAnalyticRepository>( - () => _i393.AnalyticRepository( - gh<_i866.AnalyticRemoteDataProvider>(), - gh<_i991.AuthLocalDataProvider>(), - ), - ); gh.factory<_i1020.ICategoryRepository>( () => _i869.CategoryRepository(gh<_i333.CategoryRemoteDataProvider>()), ); @@ -234,6 +241,13 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i183.CategoryLoaderBloc>( () => _i183.CategoryLoaderBloc(gh<_i1020.ICategoryRepository>()), ); + gh.factory<_i477.IAnalyticRepository>( + () => _i393.AnalyticRepository( + gh<_i866.AnalyticRemoteDataProvider>(), + gh<_i991.AuthLocalDataProvider>(), + gh<_i850.OutletLocalDataProvider>(), + ), + ); gh.factory<_i473.HomeBloc>( () => _i473.HomeBloc(gh<_i477.IAnalyticRepository>()), ); diff --git a/lib/presentation/pages/home/home_page.dart b/lib/presentation/pages/home/home_page.dart index 721e904..d19d6eb 100644 --- a/lib/presentation/pages/home/home_page.dart +++ b/lib/presentation/pages/home/home_page.dart @@ -5,6 +5,7 @@ import 'package:line_icons/line_icons.dart'; import '../../../application/home/home_bloc.dart'; import '../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart'; +import '../../../application/outlet/selected_outlet/selected_outlet_bloc.dart'; import '../../../common/constant/app_constant.dart'; import '../../../common/theme/theme.dart'; import '../../../injection.dart'; @@ -33,6 +34,10 @@ class HomePage extends StatefulWidget implements AutoRouteWrapper { create: (context) => getIt() ..add(const OutletListLoaderEvent.fetched()), ), + BlocProvider( + create: (context) => getIt() + ..add(const SelectedOutletEvent.loaded()), + ), ], child: this, ); diff --git a/lib/presentation/pages/home/widgets/omset_balance.dart b/lib/presentation/pages/home/widgets/omset_balance.dart index 3240267..c760e81 100644 --- a/lib/presentation/pages/home/widgets/omset_balance.dart +++ b/lib/presentation/pages/home/widgets/omset_balance.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:line_icons/line_icon.dart'; import 'package:line_icons/line_icons.dart'; import '../../../../../../common/theme/theme.dart'; +import '../../../../application/outlet/selected_outlet/selected_outlet_bloc.dart'; import '../../../../common/extension/extension.dart'; import '../../../../domain/user/user.dart'; import '../../../components/spacer/spacer.dart'; @@ -185,40 +187,46 @@ class _HomeOmsetBalanceState extends State { GestureDetector _top(BuildContext context) { return GestureDetector( onTap: () {}, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - gradient: LinearGradient(colors: AppColor.primaryGradient), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(16), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Semua Outlet', - style: AppStyle.sm.copyWith( - color: AppColor.white, - fontWeight: FontWeight.w600, - letterSpacing: 0.3, - ), - ), - ], + child: BlocBuilder( + builder: (context, state) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + gradient: LinearGradient(colors: AppColor.primaryGradient), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), ), ), - SpaceWidth(6), - LineIcon( - LineIcons.alternateExchange, - color: AppColor.white, - size: 14, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + state.displayName, + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + SpaceWidth(6), + LineIcon( + LineIcons.alternateExchange, + color: AppColor.white, + size: 14, + ), + ], ), - ], - ), + ); + }, ), ); } diff --git a/lib/presentation/pages/home/widgets/promo_banner.dart b/lib/presentation/pages/home/widgets/promo_banner.dart index a3a370a..c2ad529 100644 --- a/lib/presentation/pages/home/widgets/promo_banner.dart +++ b/lib/presentation/pages/home/widgets/promo_banner.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart'; +import '../../../../application/outlet/selected_outlet/selected_outlet_bloc.dart'; import '../../../../common/theme/theme.dart'; import '../../../../domain/outlet/outlet.dart'; import '../../../components/spacer/spacer.dart'; @@ -13,32 +14,54 @@ class HomePromoBanner extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (context, state) { - if (state.isFetching && state.outlets.isEmpty) { + builder: (context, outletListState) { + if (outletListState.isFetching && outletListState.outlets.isEmpty) { return const _PromoBannerSkeleton(); } - if (state.outlets.isEmpty) { + if (outletListState.outlets.isEmpty) { return const SizedBox.shrink(); } - return Padding( - padding: const EdgeInsets.fromLTRB( - AppValue.padding, - 24, - AppValue.padding, - 0, - ), - child: Row( - children: [ - for (int i = 0; i < state.outlets.length; i++) ...[ - Expanded( - child: _OutletCard(outlet: state.outlets[i]), - ), - if (i < state.outlets.length - 1) const SpaceWidth(12), - ], - ], - ), + return BlocBuilder( + builder: (context, selectedState) { + return Padding( + padding: const EdgeInsets.fromLTRB( + AppValue.padding, + 24, + AppValue.padding, + 0, + ), + child: Row( + children: [ + for (int i = 0; i < outletListState.outlets.length; i++) ...[ + Expanded( + child: _OutletCard( + outlet: outletListState.outlets[i], + isSelected: selectedState.selectedOutletId == + outletListState.outlets[i].id, + onTap: () { + final tapped = outletListState.outlets[i]; + if (selectedState.selectedOutletId == tapped.id) { + // Tap outlet yang sama → deselect (Semua Outlet) + context + .read() + .add(const SelectedOutletEvent.cleared()); + } else { + context + .read() + .add(SelectedOutletEvent.selected(tapped)); + } + }, + ), + ), + if (i < outletListState.outlets.length - 1) + const SpaceWidth(12), + ], + ], + ), + ); + }, ); }, ); @@ -47,42 +70,73 @@ class HomePromoBanner extends StatelessWidget { class _OutletCard extends StatelessWidget { final Outlet outlet; + final bool isSelected; + final VoidCallback onTap; - const _OutletCard({required this.outlet}); + const _OutletCard({ + required this.outlet, + required this.isSelected, + required this.onTap, + }); @override Widget build(BuildContext context) { - return ParticleCard( - height: 110, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - decorationOpacity: 0.8, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - const Spacer(), - _buildHealthIndicator(outlet.isActive), - ], + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected ? AppColor.white : Colors.transparent, + width: 2, ), - const SpaceHeight(6), - Text( - outlet.name, - style: AppStyle.sm.copyWith( - color: AppColor.white, - fontWeight: FontWeight.w800, - height: 1.25, + boxShadow: isSelected + ? [ + BoxShadow( + color: AppColor.white.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ] + : [], + ), + child: Opacity( + opacity: isSelected ? 1.0 : 0.55, + child: ParticleCard( + height: 110, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decorationOpacity: 0.8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Spacer(), + _buildStatusBadge(outlet.isActive), + ], + ), + const SpaceHeight(6), + Text( + outlet.name, + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w800, + height: 1.25, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ], ), - maxLines: 3, - overflow: TextOverflow.ellipsis, ), - ], + ), ), ); } - Widget _buildHealthIndicator(bool isActive) { + Widget _buildStatusBadge(bool isActive) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( diff --git a/lib/presentation/pages/purchase/purchase_page.dart b/lib/presentation/pages/purchase/purchase_page.dart index fa7558c..770a38c 100644 --- a/lib/presentation/pages/purchase/purchase_page.dart +++ b/lib/presentation/pages/purchase/purchase_page.dart @@ -29,38 +29,6 @@ class _PurchasePageState extends State ]; final List> purchaseData = [ - { - 'id': 'PO-001', - 'supplier': 'PT. Sumber Rezeki', - 'date': '15 Aug 2025', - 'total': 2500000, - 'status': 'Completed', - 'items': 15, - }, - { - 'id': 'PO-002', - 'supplier': 'CV. Maju Jaya', - 'date': '14 Aug 2025', - 'total': 1750000, - 'status': 'Pending', - 'items': 8, - }, - { - 'id': 'PO-003', - 'supplier': 'PT. Global Supply', - 'date': '13 Aug 2025', - 'total': 3200000, - 'status': 'Completed', - 'items': 22, - }, - { - 'id': 'PO-004', - 'supplier': 'UD. Berkah Mandiri', - 'date': '12 Aug 2025', - 'total': 890000, - 'status': 'Cancelled', - 'items': 5, - }, ]; @override @@ -107,7 +75,7 @@ class _PurchasePageState extends State Expanded( child: PurchaseStatCard( title: context.lang.total_purchase, - value: 'Rp 8.340.000', + value: 'Rp 0', icon: LineIcons.shoppingCart, iconColor: AppColor.success, cardAnimation: cardAnimation, @@ -117,7 +85,7 @@ class _PurchasePageState extends State Expanded( child: PurchaseStatCard( title: context.lang.pending_order, - value: '3 ${context.lang.orders}', + value: '0 ${context.lang.orders}', icon: LineIcons.clock, iconColor: AppColor.warning, cardAnimation: cardAnimation,