selected outlet

This commit is contained in:
efrilm 2026-05-12 17:09:50 +07:00
parent 3541fc725c
commit ba2f0cd265
17 changed files with 1034 additions and 211 deletions

View File

@ -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<SelectedOutletEvent, SelectedOutletState> {
final OutletLocalDataProvider _localDataProvider;
SelectedOutletBloc(this._localDataProvider)
: super(SelectedOutletState.initial()) {
on<SelectedOutletEvent>(_onSelectedOutletEvent);
}
Future<void> _onSelectedOutletEvent(
SelectedOutletEvent event,
Emitter<SelectedOutletState> 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());
},
);
}
}

View File

@ -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>(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<TResult extends Object?>({
required TResult Function() loaded,
required TResult Function(Outlet outlet) selected,
required TResult Function() cleared,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loaded,
TResult? Function(Outlet outlet)? selected,
TResult? Function()? cleared,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loaded,
TResult Function(Outlet outlet)? selected,
TResult Function()? cleared,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loaded value) loaded,
required TResult Function(_Selected value) selected,
required TResult Function(_Cleared value) cleared,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Selected value)? selected,
TResult? Function(_Cleared value)? cleared,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
required TResult Function() loaded,
required TResult Function(Outlet outlet) selected,
required TResult Function() cleared,
}) {
return loaded();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loaded,
TResult? Function(Outlet outlet)? selected,
TResult? Function()? cleared,
}) {
return loaded?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Selected value)? selected,
TResult? Function(_Cleared value)? cleared,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
required TResult Function() loaded,
required TResult Function(Outlet outlet) selected,
required TResult Function() cleared,
}) {
return selected(outlet);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loaded,
TResult? Function(Outlet outlet)? selected,
TResult? Function()? cleared,
}) {
return selected?.call(outlet);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Selected value)? selected,
TResult? Function(_Cleared value)? cleared,
}) {
return selected?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
required TResult Function() loaded,
required TResult Function(Outlet outlet) selected,
required TResult Function() cleared,
}) {
return cleared();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loaded,
TResult? Function(Outlet outlet)? selected,
TResult? Function()? cleared,
}) {
return cleared?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Selected value)? selected,
TResult? Function(_Cleared value)? cleared,
}) {
return cleared?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<SelectedOutletState> 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;
}

View File

@ -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;
}

View File

@ -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';
}

View File

@ -2,4 +2,5 @@ class LocalStorageKey {
static const String lang = 'lang'; static const String lang = 'lang';
static const String token = 'token'; static const String token = 'token';
static const String user = 'user'; static const String user = 'user';
static const String selectedOutletId = 'selected_outlet_id';
} }

View File

@ -6,35 +6,42 @@ abstract class IAnalyticRepository {
Future<Either<AnalyticFailure, SalesAnalytic>> getSales({ Future<Either<AnalyticFailure, SalesAnalytic>> getSales({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, ProfitLossAnalytic>> getProfitLoss({ Future<Either<AnalyticFailure, ProfitLossAnalytic>> getProfitLoss({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, CategoryAnalytic>> getCategory({ Future<Either<AnalyticFailure, CategoryAnalytic>> getCategory({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({ Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, DashboardAnalytic>> getDashboard({ Future<Either<AnalyticFailure, DashboardAnalytic>> getDashboard({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, ProductAnalytic>> getProduct({ Future<Either<AnalyticFailure, ProductAnalytic>> getProduct({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
Future<Either<AnalyticFailure, PaymentMethodAnalytic>> getPaymentMethod({ Future<Either<AnalyticFailure, PaymentMethodAnalytic>> getPaymentMethod({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}); });
} }

View File

@ -6,6 +6,7 @@ abstract class IOrderRepository {
int limit = 10, int limit = 10,
String? status, String? status,
String? search, String? search,
String? outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
}); });

View File

@ -21,14 +21,18 @@ class AnalyticRemoteDataProvider {
Future<DC<AnalyticFailure, SalesAnalyticDto>> fetchSales({ Future<DC<AnalyticFailure, SalesAnalyticDto>> fetchSales({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.salesAnalytic, ApiPath.salesAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@ -48,14 +52,18 @@ class AnalyticRemoteDataProvider {
Future<DC<AnalyticFailure, ProfitLossAnalyticDto>> fetchProfitLoss({ Future<DC<AnalyticFailure, ProfitLossAnalyticDto>> fetchProfitLoss({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.profitLossAnalytic, ApiPath.profitLossAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@ -75,14 +83,18 @@ class AnalyticRemoteDataProvider {
Future<DC<AnalyticFailure, CategoryAnalyticDto>> fetchCategory({ Future<DC<AnalyticFailure, CategoryAnalyticDto>> fetchCategory({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.categoryAnalytic, ApiPath.categoryAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@ -128,17 +140,20 @@ class AnalyticRemoteDataProvider {
} }
Future<DC<AnalyticFailure, DashboardAnalyticDto>> fetchDashboard({ Future<DC<AnalyticFailure, DashboardAnalyticDto>> fetchDashboard({
required String outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.dashboardAnalytic, ApiPath.dashboardAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@ -156,17 +171,20 @@ class AnalyticRemoteDataProvider {
} }
Future<DC<AnalyticFailure, ProductAnalyticDto>> fetchProduct({ Future<DC<AnalyticFailure, ProductAnalyticDto>> fetchProduct({
required String outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.productAnalytic, ApiPath.productAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );
@ -184,17 +202,20 @@ class AnalyticRemoteDataProvider {
} }
Future<DC<AnalyticFailure, PaymentMethodAnalyticDto>> fetchPaymentMethod({ Future<DC<AnalyticFailure, PaymentMethodAnalyticDto>> fetchPaymentMethod({
required String outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final Map<String, dynamic> params = {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (outletId != null) params['outlet_id'] = outletId;
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.paymentMethodAnalytic, ApiPath.paymentMethodAnalytic,
params: { params: params,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(), headers: getAuthorizationHeader(),
); );

View File

@ -5,36 +5,43 @@ import 'package:injectable/injectable.dart';
import '../../../domain/analytic/analytic.dart'; import '../../../domain/analytic/analytic.dart';
import '../../../domain/analytic/repositories/i_analytic_repository.dart'; import '../../../domain/analytic/repositories/i_analytic_repository.dart';
import '../../../domain/user/user.dart';
import '../../auth/datasources/local_data_provider.dart'; import '../../auth/datasources/local_data_provider.dart';
import '../../outlet/datasource/local_data_provider.dart';
import '../datasource/remote_data_provider.dart'; import '../datasource/remote_data_provider.dart';
@Injectable(as: IAnalyticRepository) @Injectable(as: IAnalyticRepository)
class AnalyticRepository implements IAnalyticRepository { class AnalyticRepository implements IAnalyticRepository {
final AnalyticRemoteDataProvider _dataProvider; final AnalyticRemoteDataProvider _dataProvider;
final AuthLocalDataProvider _authLocalDataProvider; final AuthLocalDataProvider _authLocalDataProvider;
final OutletLocalDataProvider _outletLocalDataProvider;
final String _logName = 'AnalyticRepository'; 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 @override
Future<Either<AnalyticFailure, SalesAnalytic>> getSales({ Future<Either<AnalyticFailure, SalesAnalytic>> getSales({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final result = await _dataProvider.fetchSales( final result = await _dataProvider.fetchSales(
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getSalesError', name: _logName, error: e, stackTrace: s); log('getSalesError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -45,20 +52,17 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, ProfitLossAnalytic>> getProfitLoss({ Future<Either<AnalyticFailure, ProfitLossAnalytic>> getProfitLoss({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final result = await _dataProvider.fetchProfitLoss( final result = await _dataProvider.fetchProfitLoss(
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getProfitLossError', name: _logName, error: e, stackTrace: s); log('getProfitLossError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -69,20 +73,17 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, CategoryAnalytic>> getCategory({ Future<Either<AnalyticFailure, CategoryAnalytic>> getCategory({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
final result = await _dataProvider.fetchCategory( final result = await _dataProvider.fetchCategory(
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getCategoryError', name: _logName, error: e, stackTrace: s); log('getCategoryError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -93,23 +94,20 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({ Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
User currentUser = await _authLocalDataProvider.currentUser(); final currentUser = await _authLocalDataProvider.currentUser();
final resolvedId = _resolveOutletId(outletId) ?? currentUser.outletId;
final result = await _dataProvider.fetchInventory( final result = await _dataProvider.fetchInventory(
outletId: currentUser.outletId, outletId: resolvedId,
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getInventoryError', name: _logName, error: e, stackTrace: s); log('getInventoryError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -120,23 +118,17 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, DashboardAnalytic>> getDashboard({ Future<Either<AnalyticFailure, DashboardAnalytic>> getDashboard({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
User currentUser = await _authLocalDataProvider.currentUser();
final result = await _dataProvider.fetchDashboard( final result = await _dataProvider.fetchDashboard(
outletId: currentUser.outletId,
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getDashboardError', name: _logName, error: e, stackTrace: s); log('getDashboardError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -147,23 +139,17 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, ProductAnalytic>> getProduct({ Future<Either<AnalyticFailure, ProductAnalytic>> getProduct({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
User currentUser = await _authLocalDataProvider.currentUser();
final result = await _dataProvider.fetchProduct( final result = await _dataProvider.fetchProduct(
outletId: currentUser.outletId,
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getProductError', name: _logName, error: e, stackTrace: s); log('getProductError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
@ -174,23 +160,17 @@ class AnalyticRepository implements IAnalyticRepository {
Future<Either<AnalyticFailure, PaymentMethodAnalytic>> getPaymentMethod({ Future<Either<AnalyticFailure, PaymentMethodAnalytic>> getPaymentMethod({
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
String? outletId,
}) async { }) async {
try { try {
User currentUser = await _authLocalDataProvider.currentUser();
final result = await _dataProvider.fetchPaymentMethod( final result = await _dataProvider.fetchPaymentMethod(
outletId: currentUser.outletId,
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
outletId: _resolveOutletId(outletId),
); );
if (result.hasError) { if (result.hasError) return left(result.error!);
return left(result.error!); return right(result.data!.toDomain());
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) { } catch (e, s) {
log('getPaymentMethodError', name: _logName, error: e, stackTrace: s); log('getPaymentMethodError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());

View File

@ -23,6 +23,7 @@ class OrderRemoteDataProvider {
int limit = 10, int limit = 10,
String? status, String? status,
String? search, String? search,
String? outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
}) async { }) async {
@ -42,6 +43,10 @@ class OrderRemoteDataProvider {
params['search'] = search; params['search'] = search;
} }
// if (outletId != null) {
// params['outlet_id'] = outletId;
// }
final response = await _apiClient.get( final response = await _apiClient.get(
ApiPath.order, ApiPath.order,
params: params, params: params,

View File

@ -4,14 +4,16 @@ import 'package:dartz/dartz.dart' hide Order;
import 'package:injectable/injectable.dart' hide Order; import 'package:injectable/injectable.dart' hide Order;
import '../../../domain/order/order.dart'; import '../../../domain/order/order.dart';
import '../../outlet/datasource/local_data_provider.dart';
import '../datasource/remote_data_provider.dart'; import '../datasource/remote_data_provider.dart';
@Injectable(as: IOrderRepository) @Injectable(as: IOrderRepository)
class OrderRepository implements IOrderRepository { class OrderRepository implements IOrderRepository {
final OrderRemoteDataProvider _dataProvider; final OrderRemoteDataProvider _dataProvider;
final OutletLocalDataProvider _outletLocalDataProvider;
final String _logName = 'OrderRepository'; final String _logName = 'OrderRepository';
OrderRepository(this._dataProvider); OrderRepository(this._dataProvider, this._outletLocalDataProvider);
@override @override
Future<Either<OrderFailure, List<Order>>> get({ Future<Either<OrderFailure, List<Order>>> get({
@ -19,15 +21,20 @@ class OrderRepository implements IOrderRepository {
int limit = 20, int limit = 20,
String? status, String? status,
String? search, String? search,
String? outletId,
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
}) async { }) async {
try { try {
final resolvedOutletId =
outletId ?? _outletLocalDataProvider.getSelectedOutletId();
final result = await _dataProvider.fetch( final result = await _dataProvider.fetch(
page: page, page: page,
limit: limit, limit: limit,
status: status, status: status,
search: search, search: search,
outletId: resolvedOutletId,
dateFrom: dateFrom, dateFrom: dateFrom,
dateTo: dateTo, dateTo: dateTo,
); );
@ -36,9 +43,9 @@ class OrderRepository implements IOrderRepository {
return left(result.error!); 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) { } catch (e, s) {
log('getOrderError', name: _logName, error: e, stackTrace: s); log('getOrderError', name: _logName, error: e, stackTrace: s);
return left(const OrderFailure.unexpectedError()); return left(const OrderFailure.unexpectedError());

View File

@ -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<void> saveSelectedOutletId(String outletId) async {
await _sharedPreferences.setString(
LocalStorageKey.selectedOutletId,
outletId,
);
}
String? getSelectedOutletId() {
return _sharedPreferences.getString(LocalStorageKey.selectedOutletId);
}
Future<void> deleteSelectedOutletId() async {
await _sharedPreferences.remove(LocalStorageKey.selectedOutletId);
}
}

View File

@ -41,6 +41,8 @@ import 'package:apskel_owner_flutter/application/outlet/current_outlet_loader/cu
as _i337; as _i337;
import 'package:apskel_owner_flutter/application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart' import 'package:apskel_owner_flutter/application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart'
as _i877; 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' import 'package:apskel_owner_flutter/application/product/product_loader/product_loader_bloc.dart'
as _i458; as _i458;
import 'package:apskel_owner_flutter/application/report/inventory_report/inventory_report_bloc.dart' 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; as _i130;
import 'package:apskel_owner_flutter/infrastructure/order/repositories/order_repository.dart' import 'package:apskel_owner_flutter/infrastructure/order/repositories/order_repository.dart'
as _i641; 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' import 'package:apskel_owner_flutter/infrastructure/outlet/datasource/remote_data_provider.dart'
as _i27; as _i27;
import 'package:apskel_owner_flutter/infrastructure/outlet/repositories/outlet_repository.dart' import 'package:apskel_owner_flutter/infrastructure/outlet/repositories/outlet_repository.dart'
@ -161,6 +165,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i991.AuthLocalDataProvider>( gh.factory<_i991.AuthLocalDataProvider>(
() => _i991.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), () => _i991.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
); );
gh.factory<_i850.OutletLocalDataProvider>(
() => _i850.OutletLocalDataProvider(gh<_i460.SharedPreferences>()),
);
gh.lazySingleton<_i115.ApiClient>( gh.lazySingleton<_i115.ApiClient>(
() => _i115.ApiClient(gh<_i361.Dio>(), gh<_i6.Env>()), () => _i115.ApiClient(gh<_i361.Dio>(), gh<_i6.Env>()),
); );
@ -192,15 +199,15 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i48.ICustomerRepository>( gh.factory<_i48.ICustomerRepository>(
() => _i550.CustomerRepository(gh<_i1006.CustomerRemoteDataProvider>()), () => _i550.CustomerRepository(gh<_i1006.CustomerRemoteDataProvider>()),
); );
gh.factory<_i219.IOrderRepository>(
() => _i641.OrderRepository(gh<_i130.OrderRemoteDataProvider>()),
);
gh.factory<_i49.IAuthRepository>( gh.factory<_i49.IAuthRepository>(
() => _i1035.AuthRepository( () => _i1035.AuthRepository(
gh<_i991.AuthLocalDataProvider>(), gh<_i991.AuthLocalDataProvider>(),
gh<_i17.AuthRemoteDataProvider>(), gh<_i17.AuthRemoteDataProvider>(),
), ),
); );
gh.factory<_i678.SelectedOutletBloc>(
() => _i678.SelectedOutletBloc(gh<_i850.OutletLocalDataProvider>()),
);
gh.factory<_i635.IUserRepository>( gh.factory<_i635.IUserRepository>(
() => _i754.UserRepository( () => _i754.UserRepository(
gh<_i785.UserRemoteDataProvider>(), gh<_i785.UserRemoteDataProvider>(),
@ -210,15 +217,15 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i419.IProductRepository>( gh.factory<_i419.IProductRepository>(
() => _i121.ProductRepository(gh<_i823.ProductRemoteDataProvider>()), () => _i121.ProductRepository(gh<_i823.ProductRemoteDataProvider>()),
); );
gh.factory<_i219.IOrderRepository>(
() => _i641.OrderRepository(
gh<_i130.OrderRemoteDataProvider>(),
gh<_i850.OutletLocalDataProvider>(),
),
);
gh.factory<_i972.CustomerLoaderBloc>( gh.factory<_i972.CustomerLoaderBloc>(
() => _i972.CustomerLoaderBloc(gh<_i48.ICustomerRepository>()), () => _i972.CustomerLoaderBloc(gh<_i48.ICustomerRepository>()),
); );
gh.factory<_i477.IAnalyticRepository>(
() => _i393.AnalyticRepository(
gh<_i866.AnalyticRemoteDataProvider>(),
gh<_i991.AuthLocalDataProvider>(),
),
);
gh.factory<_i1020.ICategoryRepository>( gh.factory<_i1020.ICategoryRepository>(
() => _i869.CategoryRepository(gh<_i333.CategoryRemoteDataProvider>()), () => _i869.CategoryRepository(gh<_i333.CategoryRemoteDataProvider>()),
); );
@ -234,6 +241,13 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i183.CategoryLoaderBloc>( gh.factory<_i183.CategoryLoaderBloc>(
() => _i183.CategoryLoaderBloc(gh<_i1020.ICategoryRepository>()), () => _i183.CategoryLoaderBloc(gh<_i1020.ICategoryRepository>()),
); );
gh.factory<_i477.IAnalyticRepository>(
() => _i393.AnalyticRepository(
gh<_i866.AnalyticRemoteDataProvider>(),
gh<_i991.AuthLocalDataProvider>(),
gh<_i850.OutletLocalDataProvider>(),
),
);
gh.factory<_i473.HomeBloc>( gh.factory<_i473.HomeBloc>(
() => _i473.HomeBloc(gh<_i477.IAnalyticRepository>()), () => _i473.HomeBloc(gh<_i477.IAnalyticRepository>()),
); );

View File

@ -5,6 +5,7 @@ import 'package:line_icons/line_icons.dart';
import '../../../application/home/home_bloc.dart'; import '../../../application/home/home_bloc.dart';
import '../../../application/outlet/outlet_list_loader/outlet_list_loader_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/constant/app_constant.dart';
import '../../../common/theme/theme.dart'; import '../../../common/theme/theme.dart';
import '../../../injection.dart'; import '../../../injection.dart';
@ -33,6 +34,10 @@ class HomePage extends StatefulWidget implements AutoRouteWrapper {
create: (context) => getIt<OutletListLoaderBloc>() create: (context) => getIt<OutletListLoaderBloc>()
..add(const OutletListLoaderEvent.fetched()), ..add(const OutletListLoaderEvent.fetched()),
), ),
BlocProvider(
create: (context) => getIt<SelectedOutletBloc>()
..add(const SelectedOutletEvent.loaded()),
),
], ],
child: this, child: this,
); );

View File

@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:line_icons/line_icon.dart'; import 'package:line_icons/line_icon.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../../../../../../common/theme/theme.dart'; import '../../../../../../common/theme/theme.dart';
import '../../../../application/outlet/selected_outlet/selected_outlet_bloc.dart';
import '../../../../common/extension/extension.dart'; import '../../../../common/extension/extension.dart';
import '../../../../domain/user/user.dart'; import '../../../../domain/user/user.dart';
import '../../../components/spacer/spacer.dart'; import '../../../components/spacer/spacer.dart';
@ -185,40 +187,46 @@ class _HomeOmsetBalanceState extends State<HomeOmsetBalance> {
GestureDetector _top(BuildContext context) { GestureDetector _top(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () {}, onTap: () {},
child: Container( child: BlocBuilder<SelectedOutletBloc, SelectedOutletState>(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), builder: (context, state) {
decoration: BoxDecoration( return Container(
gradient: LinearGradient(colors: AppColor.primaryGradient), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
borderRadius: const BorderRadius.vertical( decoration: BoxDecoration(
top: Radius.circular(16), 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,
),
),
],
), ),
), ),
SpaceWidth(6), child: Row(
LineIcon( mainAxisSize: MainAxisSize.min,
LineIcons.alternateExchange, children: [
color: AppColor.white, Expanded(
size: 14, 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,
),
],
), ),
], );
), },
), ),
); );
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/outlet/outlet_list_loader/outlet_list_loader_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 '../../../../common/theme/theme.dart';
import '../../../../domain/outlet/outlet.dart'; import '../../../../domain/outlet/outlet.dart';
import '../../../components/spacer/spacer.dart'; import '../../../components/spacer/spacer.dart';
@ -13,32 +14,54 @@ class HomePromoBanner extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<OutletListLoaderBloc, OutletListLoaderState>( return BlocBuilder<OutletListLoaderBloc, OutletListLoaderState>(
builder: (context, state) { builder: (context, outletListState) {
if (state.isFetching && state.outlets.isEmpty) { if (outletListState.isFetching && outletListState.outlets.isEmpty) {
return const _PromoBannerSkeleton(); return const _PromoBannerSkeleton();
} }
if (state.outlets.isEmpty) { if (outletListState.outlets.isEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Padding( return BlocBuilder<SelectedOutletBloc, SelectedOutletState>(
padding: const EdgeInsets.fromLTRB( builder: (context, selectedState) {
AppValue.padding, return Padding(
24, padding: const EdgeInsets.fromLTRB(
AppValue.padding, AppValue.padding,
0, 24,
), AppValue.padding,
child: Row( 0,
children: [ ),
for (int i = 0; i < state.outlets.length; i++) ...[ child: Row(
Expanded( children: [
child: _OutletCard(outlet: state.outlets[i]), for (int i = 0; i < outletListState.outlets.length; i++) ...[
), Expanded(
if (i < state.outlets.length - 1) const SpaceWidth(12), 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<SelectedOutletBloc>()
.add(const SelectedOutletEvent.cleared());
} else {
context
.read<SelectedOutletBloc>()
.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 { class _OutletCard extends StatelessWidget {
final Outlet outlet; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ParticleCard( return GestureDetector(
height: 110, onTap: onTap,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: AnimatedContainer(
decorationOpacity: 0.8, duration: const Duration(milliseconds: 200),
child: Column( decoration: BoxDecoration(
crossAxisAlignment: CrossAxisAlignment.start, borderRadius: BorderRadius.circular(12),
mainAxisAlignment: MainAxisAlignment.spaceBetween, border: Border.all(
children: [ color: isSelected ? AppColor.white : Colors.transparent,
Row( width: 2,
children: [
const Spacer(),
_buildHealthIndicator(outlet.isActive),
],
), ),
const SpaceHeight(6), boxShadow: isSelected
Text( ? [
outlet.name, BoxShadow(
style: AppStyle.sm.copyWith( color: AppColor.white.withOpacity(0.3),
color: AppColor.white, blurRadius: 8,
fontWeight: FontWeight.w800, offset: const Offset(0, 2),
height: 1.25, ),
]
: [],
),
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( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -29,38 +29,6 @@ class _PurchasePageState extends State<PurchasePage>
]; ];
final List<Map<String, dynamic>> purchaseData = [ final List<Map<String, dynamic>> 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 @override
@ -107,7 +75,7 @@ class _PurchasePageState extends State<PurchasePage>
Expanded( Expanded(
child: PurchaseStatCard( child: PurchaseStatCard(
title: context.lang.total_purchase, title: context.lang.total_purchase,
value: 'Rp 8.340.000', value: 'Rp 0',
icon: LineIcons.shoppingCart, icon: LineIcons.shoppingCart,
iconColor: AppColor.success, iconColor: AppColor.success,
cardAnimation: cardAnimation, cardAnimation: cardAnimation,
@ -117,7 +85,7 @@ class _PurchasePageState extends State<PurchasePage>
Expanded( Expanded(
child: PurchaseStatCard( child: PurchaseStatCard(
title: context.lang.pending_order, title: context.lang.pending_order,
value: '3 ${context.lang.orders}', value: '0 ${context.lang.orders}',
icon: LineIcons.clock, icon: LineIcons.clock,
iconColor: AppColor.warning, iconColor: AppColor.warning,
cardAnimation: cardAnimation, cardAnimation: cardAnimation,