feat: sales analytic
This commit is contained in:
parent
e7525238fe
commit
f84090c0e6
39
lib/application/sales/sales_loader/sales_loader_bloc.dart
Normal file
39
lib/application/sales/sales_loader/sales_loader_bloc.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../domain/analytic/analytic.dart';
|
||||||
|
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||||
|
|
||||||
|
part 'sales_loader_event.dart';
|
||||||
|
part 'sales_loader_state.dart';
|
||||||
|
part 'sales_loader_bloc.freezed.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class SalesLoaderBloc extends Bloc<SalesLoaderEvent, SalesLoaderState> {
|
||||||
|
final IAnalyticRepository _analyticRepository;
|
||||||
|
SalesLoaderBloc(this._analyticRepository)
|
||||||
|
: super(SalesLoaderState.initial()) {
|
||||||
|
on<SalesLoaderEvent>(_onSalesLoaderEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSalesLoaderEvent(
|
||||||
|
SalesLoaderEvent event,
|
||||||
|
Emitter<SalesLoaderState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(isFetching: true, failureOptionSales: none()));
|
||||||
|
|
||||||
|
final result = await _analyticRepository.getSales(
|
||||||
|
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||||
|
dateTo: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
var data = result.fold(
|
||||||
|
(f) => state.copyWith(failureOptionSales: optionOf(f)),
|
||||||
|
(sales) => state.copyWith(sales: sales),
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(data.copyWith(isFetching: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
// 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 'sales_loader_bloc.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SalesLoaderEvent {
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() fectched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? fectched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? fectched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Fectched value) fectched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Fectched value)? fectched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Fectched value)? fectched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SalesLoaderEventCopyWith<$Res> {
|
||||||
|
factory $SalesLoaderEventCopyWith(
|
||||||
|
SalesLoaderEvent value,
|
||||||
|
$Res Function(SalesLoaderEvent) then,
|
||||||
|
) = _$SalesLoaderEventCopyWithImpl<$Res, SalesLoaderEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SalesLoaderEventCopyWithImpl<$Res, $Val extends SalesLoaderEvent>
|
||||||
|
implements $SalesLoaderEventCopyWith<$Res> {
|
||||||
|
_$SalesLoaderEventCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$FectchedImplCopyWith<$Res> {
|
||||||
|
factory _$$FectchedImplCopyWith(
|
||||||
|
_$FectchedImpl value,
|
||||||
|
$Res Function(_$FectchedImpl) then,
|
||||||
|
) = __$$FectchedImplCopyWithImpl<$Res>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$FectchedImplCopyWithImpl<$Res>
|
||||||
|
extends _$SalesLoaderEventCopyWithImpl<$Res, _$FectchedImpl>
|
||||||
|
implements _$$FectchedImplCopyWith<$Res> {
|
||||||
|
__$$FectchedImplCopyWithImpl(
|
||||||
|
_$FectchedImpl _value,
|
||||||
|
$Res Function(_$FectchedImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$FectchedImpl implements _Fectched {
|
||||||
|
const _$FectchedImpl();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SalesLoaderEvent.fectched()';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType && other is _$FectchedImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function() fectched,
|
||||||
|
}) {
|
||||||
|
return fectched();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function()? fectched,
|
||||||
|
}) {
|
||||||
|
return fectched?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function()? fectched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (fectched != null) {
|
||||||
|
return fectched();
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_Fectched value) fectched,
|
||||||
|
}) {
|
||||||
|
return fectched(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_Fectched value)? fectched,
|
||||||
|
}) {
|
||||||
|
return fectched?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_Fectched value)? fectched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (fectched != null) {
|
||||||
|
return fectched(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Fectched implements SalesLoaderEvent {
|
||||||
|
const factory _Fectched() = _$FectchedImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SalesLoaderState {
|
||||||
|
SalesAnalytic get sales => throw _privateConstructorUsedError;
|
||||||
|
Option<AnalyticFailure> get failureOptionSales =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
bool get isFetching => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SalesLoaderStateCopyWith<SalesLoaderState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SalesLoaderStateCopyWith<$Res> {
|
||||||
|
factory $SalesLoaderStateCopyWith(
|
||||||
|
SalesLoaderState value,
|
||||||
|
$Res Function(SalesLoaderState) then,
|
||||||
|
) = _$SalesLoaderStateCopyWithImpl<$Res, SalesLoaderState>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
SalesAnalytic sales,
|
||||||
|
Option<AnalyticFailure> failureOptionSales,
|
||||||
|
bool isFetching,
|
||||||
|
});
|
||||||
|
|
||||||
|
$SalesAnalyticCopyWith<$Res> get sales;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SalesLoaderStateCopyWithImpl<$Res, $Val extends SalesLoaderState>
|
||||||
|
implements $SalesLoaderStateCopyWith<$Res> {
|
||||||
|
_$SalesLoaderStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? sales = null,
|
||||||
|
Object? failureOptionSales = null,
|
||||||
|
Object? isFetching = null,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
sales: null == sales
|
||||||
|
? _value.sales
|
||||||
|
: sales // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SalesAnalytic,
|
||||||
|
failureOptionSales: null == failureOptionSales
|
||||||
|
? _value.failureOptionSales
|
||||||
|
: failureOptionSales // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Option<AnalyticFailure>,
|
||||||
|
isFetching: null == isFetching
|
||||||
|
? _value.isFetching
|
||||||
|
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SalesAnalyticCopyWith<$Res> get sales {
|
||||||
|
return $SalesAnalyticCopyWith<$Res>(_value.sales, (value) {
|
||||||
|
return _then(_value.copyWith(sales: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SalesLoaderStateImplCopyWith<$Res>
|
||||||
|
implements $SalesLoaderStateCopyWith<$Res> {
|
||||||
|
factory _$$SalesLoaderStateImplCopyWith(
|
||||||
|
_$SalesLoaderStateImpl value,
|
||||||
|
$Res Function(_$SalesLoaderStateImpl) then,
|
||||||
|
) = __$$SalesLoaderStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
SalesAnalytic sales,
|
||||||
|
Option<AnalyticFailure> failureOptionSales,
|
||||||
|
bool isFetching,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SalesAnalyticCopyWith<$Res> get sales;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SalesLoaderStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$SalesLoaderStateCopyWithImpl<$Res, _$SalesLoaderStateImpl>
|
||||||
|
implements _$$SalesLoaderStateImplCopyWith<$Res> {
|
||||||
|
__$$SalesLoaderStateImplCopyWithImpl(
|
||||||
|
_$SalesLoaderStateImpl _value,
|
||||||
|
$Res Function(_$SalesLoaderStateImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? sales = null,
|
||||||
|
Object? failureOptionSales = null,
|
||||||
|
Object? isFetching = null,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$SalesLoaderStateImpl(
|
||||||
|
sales: null == sales
|
||||||
|
? _value.sales
|
||||||
|
: sales // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SalesAnalytic,
|
||||||
|
failureOptionSales: null == failureOptionSales
|
||||||
|
? _value.failureOptionSales
|
||||||
|
: failureOptionSales // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Option<AnalyticFailure>,
|
||||||
|
isFetching: null == isFetching
|
||||||
|
? _value.isFetching
|
||||||
|
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$SalesLoaderStateImpl implements _SalesLoaderState {
|
||||||
|
const _$SalesLoaderStateImpl({
|
||||||
|
required this.sales,
|
||||||
|
required this.failureOptionSales,
|
||||||
|
this.isFetching = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final SalesAnalytic sales;
|
||||||
|
@override
|
||||||
|
final Option<AnalyticFailure> failureOptionSales;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool isFetching;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SalesLoaderState(sales: $sales, failureOptionSales: $failureOptionSales, isFetching: $isFetching)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SalesLoaderStateImpl &&
|
||||||
|
(identical(other.sales, sales) || other.sales == sales) &&
|
||||||
|
(identical(other.failureOptionSales, failureOptionSales) ||
|
||||||
|
other.failureOptionSales == failureOptionSales) &&
|
||||||
|
(identical(other.isFetching, isFetching) ||
|
||||||
|
other.isFetching == isFetching));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, sales, failureOptionSales, isFetching);
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SalesLoaderStateImplCopyWith<_$SalesLoaderStateImpl> get copyWith =>
|
||||||
|
__$$SalesLoaderStateImplCopyWithImpl<_$SalesLoaderStateImpl>(
|
||||||
|
this,
|
||||||
|
_$identity,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SalesLoaderState implements SalesLoaderState {
|
||||||
|
const factory _SalesLoaderState({
|
||||||
|
required final SalesAnalytic sales,
|
||||||
|
required final Option<AnalyticFailure> failureOptionSales,
|
||||||
|
final bool isFetching,
|
||||||
|
}) = _$SalesLoaderStateImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SalesAnalytic get sales;
|
||||||
|
@override
|
||||||
|
Option<AnalyticFailure> get failureOptionSales;
|
||||||
|
@override
|
||||||
|
bool get isFetching;
|
||||||
|
|
||||||
|
/// Create a copy of SalesLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SalesLoaderStateImplCopyWith<_$SalesLoaderStateImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
part of 'sales_loader_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesLoaderEvent with _$SalesLoaderEvent {
|
||||||
|
const factory SalesLoaderEvent.fectched() = _Fectched;
|
||||||
|
}
|
||||||
15
lib/application/sales/sales_loader/sales_loader_state.dart
Normal file
15
lib/application/sales/sales_loader/sales_loader_state.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
part of 'sales_loader_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesLoaderState with _$SalesLoaderState {
|
||||||
|
const factory SalesLoaderState({
|
||||||
|
required SalesAnalytic sales,
|
||||||
|
required Option<AnalyticFailure> failureOptionSales,
|
||||||
|
@Default(false) bool isFetching,
|
||||||
|
}) = _SalesLoaderState;
|
||||||
|
|
||||||
|
factory SalesLoaderState.initial() => SalesLoaderState(
|
||||||
|
sales: SalesAnalytic.empty(),
|
||||||
|
failureOptionSales: none(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,4 +2,7 @@ class ApiPath {
|
|||||||
// Auth
|
// Auth
|
||||||
static const String login = '/api/v1/auth/login';
|
static const String login = '/api/v1/auth/login';
|
||||||
static const String logout = '/api/v1/auth/logout';
|
static const String logout = '/api/v1/auth/logout';
|
||||||
|
|
||||||
|
// Analytic
|
||||||
|
static const String salesAnalytic = '/api/v1/analytics/sales';
|
||||||
}
|
}
|
||||||
|
|||||||
8
lib/domain/analytic/analytic.dart
Normal file
8
lib/domain/analytic/analytic.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../../common/api/api_failure.dart';
|
||||||
|
|
||||||
|
part 'analytic.freezed.dart';
|
||||||
|
|
||||||
|
part 'entities/sales_analytic_entity.dart';
|
||||||
|
part 'failures/analytic_failure.dart';
|
||||||
1510
lib/domain/analytic/analytic.freezed.dart
Normal file
1510
lib/domain/analytic/analytic.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
70
lib/domain/analytic/entities/sales_analytic_entity.dart
Normal file
70
lib/domain/analytic/entities/sales_analytic_entity.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
part of '../analytic.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalytic with _$SalesAnalytic {
|
||||||
|
const factory SalesAnalytic({
|
||||||
|
required String organizationId,
|
||||||
|
required String outletId,
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
required String groupBy,
|
||||||
|
required SalesAnalyticSummary summary,
|
||||||
|
required List<SalesAnalyticData> data,
|
||||||
|
}) = _SalesAnalytic;
|
||||||
|
|
||||||
|
factory SalesAnalytic.empty() => SalesAnalytic(
|
||||||
|
organizationId: '',
|
||||||
|
outletId: '',
|
||||||
|
dateFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
dateTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
groupBy: '',
|
||||||
|
summary: SalesAnalyticSummary.empty(),
|
||||||
|
data: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalyticSummary with _$SalesAnalyticSummary {
|
||||||
|
const factory SalesAnalyticSummary({
|
||||||
|
required int totalSales,
|
||||||
|
required int totalOrders,
|
||||||
|
required int totalItems,
|
||||||
|
required double averageOrderValue,
|
||||||
|
required int totalTax,
|
||||||
|
required int totalDiscount,
|
||||||
|
required int netSales,
|
||||||
|
}) = _SalesAnalyticSummary;
|
||||||
|
|
||||||
|
factory SalesAnalyticSummary.empty() => const SalesAnalyticSummary(
|
||||||
|
totalSales: 0,
|
||||||
|
totalOrders: 0,
|
||||||
|
totalItems: 0,
|
||||||
|
averageOrderValue: 0,
|
||||||
|
totalTax: 0,
|
||||||
|
totalDiscount: 0,
|
||||||
|
netSales: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalyticData with _$SalesAnalyticData {
|
||||||
|
const factory SalesAnalyticData({
|
||||||
|
required DateTime date,
|
||||||
|
required int sales,
|
||||||
|
required int orders,
|
||||||
|
required int items,
|
||||||
|
required int tax,
|
||||||
|
required int discount,
|
||||||
|
required int netSales,
|
||||||
|
}) = _SalesAnalyticData;
|
||||||
|
|
||||||
|
factory SalesAnalyticData.empty() => SalesAnalyticData(
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
sales: 0,
|
||||||
|
orders: 0,
|
||||||
|
items: 0,
|
||||||
|
tax: 0,
|
||||||
|
discount: 0,
|
||||||
|
netSales: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
10
lib/domain/analytic/failures/analytic_failure.dart
Normal file
10
lib/domain/analytic/failures/analytic_failure.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
part of '../analytic.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class AnalyticFailure with _$AnalyticFailure {
|
||||||
|
const factory AnalyticFailure.serverError(ApiFailure failure) = _ServerError;
|
||||||
|
const factory AnalyticFailure.unexpectedError() = _UnexpectedError;
|
||||||
|
const factory AnalyticFailure.empty() = _Empty;
|
||||||
|
const factory AnalyticFailure.dynamicErrorMessage(String erroMessage) =
|
||||||
|
_DynamicErrorMessage;
|
||||||
|
}
|
||||||
10
lib/domain/analytic/repositories/i_analytic_repository.dart
Normal file
10
lib/domain/analytic/repositories/i_analytic_repository.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
|
import '../analytic.dart';
|
||||||
|
|
||||||
|
abstract class IAnalyticRepository {
|
||||||
|
Future<Either<AnalyticFailure, SalesAnalytic>> getSales({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
});
|
||||||
|
}
|
||||||
8
lib/infrastructure/analytic/analytic_dtos.dart
Normal file
8
lib/infrastructure/analytic/analytic_dtos.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../../domain/analytic/analytic.dart';
|
||||||
|
|
||||||
|
part 'analytic_dtos.freezed.dart';
|
||||||
|
part 'analytic_dtos.g.dart';
|
||||||
|
|
||||||
|
part 'dto/sales_analytic_dto.dart';
|
||||||
1019
lib/infrastructure/analytic/analytic_dtos.freezed.dart
Normal file
1019
lib/infrastructure/analytic/analytic_dtos.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
89
lib/infrastructure/analytic/analytic_dtos.g.dart
Normal file
89
lib/infrastructure/analytic/analytic_dtos.g.dart
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'analytic_dtos.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SalesAnalyticDtoImpl _$$SalesAnalyticDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$SalesAnalyticDtoImpl(
|
||||||
|
organizationId: json['organization_id'] as String?,
|
||||||
|
outletId: json['outlet_id'] as String?,
|
||||||
|
dateFrom: json['date_from'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['date_from'] as String),
|
||||||
|
dateTo: json['date_to'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['date_to'] as String),
|
||||||
|
groupBy: json['group_by'] as String?,
|
||||||
|
summary: json['summary'] == null
|
||||||
|
? null
|
||||||
|
: SalesAnalyticSummaryDto.fromJson(
|
||||||
|
json['summary'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
data: (json['data'] as List<dynamic>?)
|
||||||
|
?.map((e) => SalesAnalyticDataDto.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SalesAnalyticDtoImplToJson(
|
||||||
|
_$SalesAnalyticDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'organization_id': instance.organizationId,
|
||||||
|
'outlet_id': instance.outletId,
|
||||||
|
'date_from': instance.dateFrom?.toIso8601String(),
|
||||||
|
'date_to': instance.dateTo?.toIso8601String(),
|
||||||
|
'group_by': instance.groupBy,
|
||||||
|
'summary': instance.summary,
|
||||||
|
'data': instance.data,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SalesAnalyticSummaryDtoImpl _$$SalesAnalyticSummaryDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$SalesAnalyticSummaryDtoImpl(
|
||||||
|
totalSales: json['total_sales'] as num?,
|
||||||
|
totalOrders: json['total_orders'] as num?,
|
||||||
|
totalItems: json['total_items'] as num?,
|
||||||
|
averageOrderValue: json['average_order_value'] as num?,
|
||||||
|
totalTax: json['total_tax'] as num?,
|
||||||
|
totalDiscount: json['total_discount'] as num?,
|
||||||
|
netSales: json['net_sales'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SalesAnalyticSummaryDtoImplToJson(
|
||||||
|
_$SalesAnalyticSummaryDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'total_sales': instance.totalSales,
|
||||||
|
'total_orders': instance.totalOrders,
|
||||||
|
'total_items': instance.totalItems,
|
||||||
|
'average_order_value': instance.averageOrderValue,
|
||||||
|
'total_tax': instance.totalTax,
|
||||||
|
'total_discount': instance.totalDiscount,
|
||||||
|
'net_sales': instance.netSales,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SalesAnalyticDataDtoImpl _$$SalesAnalyticDataDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$SalesAnalyticDataDtoImpl(
|
||||||
|
date: json['date'] == null ? null : DateTime.parse(json['date'] as String),
|
||||||
|
sales: json['sales'] as num?,
|
||||||
|
orders: json['orders'] as num?,
|
||||||
|
items: json['items'] as num?,
|
||||||
|
tax: json['tax'] as num?,
|
||||||
|
discount: json['discount'] as num?,
|
||||||
|
netSales: json['net_sales'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SalesAnalyticDataDtoImplToJson(
|
||||||
|
_$SalesAnalyticDataDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'date': instance.date?.toIso8601String(),
|
||||||
|
'sales': instance.sales,
|
||||||
|
'orders': instance.orders,
|
||||||
|
'items': instance.items,
|
||||||
|
'tax': instance.tax,
|
||||||
|
'discount': instance.discount,
|
||||||
|
'net_sales': instance.netSales,
|
||||||
|
};
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:data_channel/data_channel.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../common/api/api_client.dart';
|
||||||
|
import '../../../common/api/api_failure.dart';
|
||||||
|
import '../../../common/extension/extension.dart';
|
||||||
|
import '../../../common/url/api_path.dart';
|
||||||
|
import '../../../domain/analytic/analytic.dart';
|
||||||
|
import '../analytic_dtos.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class AnalyticRemoteDataProvider {
|
||||||
|
final ApiClient _apiClient;
|
||||||
|
final String _logName = "AnalyticRemoteDataProvider";
|
||||||
|
|
||||||
|
AnalyticRemoteDataProvider(this._apiClient);
|
||||||
|
|
||||||
|
Future<DC<AnalyticFailure, SalesAnalyticDto>> fetchSales({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _apiClient.get(
|
||||||
|
ApiPath.salesAnalytic,
|
||||||
|
params: {
|
||||||
|
'date_from': dateFrom.toServerDate,
|
||||||
|
'date_to': dateTo.toServerDate,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data['data'] == null) {
|
||||||
|
return DC.error(AnalyticFailure.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
final dto = SalesAnalyticDto.fromJson(response.data['data']);
|
||||||
|
|
||||||
|
return DC.data(dto);
|
||||||
|
} on ApiFailure catch (e, s) {
|
||||||
|
log('fetchSalesError', name: _logName, error: e, stackTrace: s);
|
||||||
|
return DC.error(AnalyticFailure.serverError(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
lib/infrastructure/analytic/dto/sales_analytic_dto.dart
Normal file
85
lib/infrastructure/analytic/dto/sales_analytic_dto.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
part of '../analytic_dtos.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalyticDto with _$SalesAnalyticDto {
|
||||||
|
const SalesAnalyticDto._();
|
||||||
|
|
||||||
|
const factory SalesAnalyticDto({
|
||||||
|
@JsonKey(name: 'organization_id') String? organizationId,
|
||||||
|
@JsonKey(name: 'outlet_id') String? outletId,
|
||||||
|
@JsonKey(name: 'date_from') DateTime? dateFrom,
|
||||||
|
@JsonKey(name: 'date_to') DateTime? dateTo,
|
||||||
|
@JsonKey(name: 'group_by') String? groupBy,
|
||||||
|
@JsonKey(name: 'summary') SalesAnalyticSummaryDto? summary,
|
||||||
|
@JsonKey(name: 'data') List<SalesAnalyticDataDto>? data,
|
||||||
|
}) = _SalesAnalyticDto;
|
||||||
|
|
||||||
|
factory SalesAnalyticDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SalesAnalyticDtoFromJson(json);
|
||||||
|
|
||||||
|
SalesAnalytic toDomain() => SalesAnalytic(
|
||||||
|
organizationId: organizationId ?? '',
|
||||||
|
outletId: outletId ?? '',
|
||||||
|
dateFrom: dateFrom ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
dateTo: dateTo ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
groupBy: groupBy ?? '',
|
||||||
|
summary: summary?.toDomain() ?? SalesAnalyticSummary.empty(),
|
||||||
|
data: data?.map((e) => e.toDomain()).toList() ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalyticSummaryDto with _$SalesAnalyticSummaryDto {
|
||||||
|
const SalesAnalyticSummaryDto._();
|
||||||
|
|
||||||
|
const factory SalesAnalyticSummaryDto({
|
||||||
|
@JsonKey(name: 'total_sales') num? totalSales,
|
||||||
|
@JsonKey(name: 'total_orders') num? totalOrders,
|
||||||
|
@JsonKey(name: 'total_items') num? totalItems,
|
||||||
|
@JsonKey(name: 'average_order_value') num? averageOrderValue,
|
||||||
|
@JsonKey(name: 'total_tax') num? totalTax,
|
||||||
|
@JsonKey(name: 'total_discount') num? totalDiscount,
|
||||||
|
@JsonKey(name: 'net_sales') num? netSales,
|
||||||
|
}) = _SalesAnalyticSummaryDto;
|
||||||
|
|
||||||
|
factory SalesAnalyticSummaryDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SalesAnalyticSummaryDtoFromJson(json);
|
||||||
|
|
||||||
|
SalesAnalyticSummary toDomain() => SalesAnalyticSummary(
|
||||||
|
totalSales: totalSales?.toInt() ?? 0,
|
||||||
|
totalOrders: totalOrders?.toInt() ?? 0,
|
||||||
|
totalItems: totalItems?.toInt() ?? 0,
|
||||||
|
averageOrderValue: averageOrderValue?.toDouble() ?? 0,
|
||||||
|
totalTax: totalTax?.toInt() ?? 0,
|
||||||
|
totalDiscount: totalDiscount?.toInt() ?? 0,
|
||||||
|
netSales: netSales?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SalesAnalyticDataDto with _$SalesAnalyticDataDto {
|
||||||
|
const SalesAnalyticDataDto._();
|
||||||
|
|
||||||
|
const factory SalesAnalyticDataDto({
|
||||||
|
@JsonKey(name: 'date') DateTime? date,
|
||||||
|
@JsonKey(name: 'sales') num? sales,
|
||||||
|
@JsonKey(name: 'orders') num? orders,
|
||||||
|
@JsonKey(name: 'items') num? items,
|
||||||
|
@JsonKey(name: 'tax') num? tax,
|
||||||
|
@JsonKey(name: 'discount') num? discount,
|
||||||
|
@JsonKey(name: 'net_sales') num? netSales,
|
||||||
|
}) = _SalesAnalyticDataDto;
|
||||||
|
|
||||||
|
factory SalesAnalyticDataDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SalesAnalyticDataDtoFromJson(json);
|
||||||
|
|
||||||
|
SalesAnalyticData toDomain() => SalesAnalyticData(
|
||||||
|
date: date ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
sales: sales?.toInt() ?? 0,
|
||||||
|
orders: orders?.toInt() ?? 0,
|
||||||
|
items: items?.toInt() ?? 0,
|
||||||
|
tax: tax?.toInt() ?? 0,
|
||||||
|
discount: discount?.toInt() ?? 0,
|
||||||
|
netSales: netSales?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../domain/analytic/analytic.dart';
|
||||||
|
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||||
|
import '../datasource/remote_data_provider.dart';
|
||||||
|
|
||||||
|
@Injectable(as: IAnalyticRepository)
|
||||||
|
class AnalyticRepository implements IAnalyticRepository {
|
||||||
|
final AnalyticRemoteDataProvider _dataProvider;
|
||||||
|
final String _logName = 'AnalyticRepository';
|
||||||
|
|
||||||
|
AnalyticRepository(this._dataProvider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<AnalyticFailure, SalesAnalytic>> getSales({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final result = await _dataProvider.fetchSales(
|
||||||
|
dateFrom: dateFrom,
|
||||||
|
dateTo: dateTo,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.hasError) {
|
||||||
|
return left(result.error!);
|
||||||
|
}
|
||||||
|
|
||||||
|
final auth = result.data!.toDomain();
|
||||||
|
|
||||||
|
return right(auth);
|
||||||
|
} catch (e, s) {
|
||||||
|
log('getSalesError', name: _logName, error: e, stackTrace: s);
|
||||||
|
return left(const AnalyticFailure.unexpectedError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,8 @@ import 'package:apskel_owner_flutter/application/auth/logout_form/logout_form_bl
|
|||||||
as _i574;
|
as _i574;
|
||||||
import 'package:apskel_owner_flutter/application/language/language_bloc.dart'
|
import 'package:apskel_owner_flutter/application/language/language_bloc.dart'
|
||||||
as _i455;
|
as _i455;
|
||||||
|
import 'package:apskel_owner_flutter/application/sales/sales_loader/sales_loader_bloc.dart'
|
||||||
|
as _i882;
|
||||||
import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115;
|
import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115;
|
||||||
import 'package:apskel_owner_flutter/common/di/di_auto_route.dart' as _i311;
|
import 'package:apskel_owner_flutter/common/di/di_auto_route.dart' as _i311;
|
||||||
import 'package:apskel_owner_flutter/common/di/di_connectivity.dart' as _i586;
|
import 'package:apskel_owner_flutter/common/di/di_connectivity.dart' as _i586;
|
||||||
@ -25,8 +27,14 @@ import 'package:apskel_owner_flutter/common/di/di_shared_preferences.dart'
|
|||||||
as _i402;
|
as _i402;
|
||||||
import 'package:apskel_owner_flutter/common/network/network_client.dart'
|
import 'package:apskel_owner_flutter/common/network/network_client.dart'
|
||||||
as _i543;
|
as _i543;
|
||||||
|
import 'package:apskel_owner_flutter/domain/analytic/repositories/i_analytic_repository.dart'
|
||||||
|
as _i477;
|
||||||
import 'package:apskel_owner_flutter/domain/auth/auth.dart' as _i49;
|
import 'package:apskel_owner_flutter/domain/auth/auth.dart' as _i49;
|
||||||
import 'package:apskel_owner_flutter/env.dart' as _i6;
|
import 'package:apskel_owner_flutter/env.dart' as _i6;
|
||||||
|
import 'package:apskel_owner_flutter/infrastructure/analytic/datasource/remote_data_provider.dart'
|
||||||
|
as _i866;
|
||||||
|
import 'package:apskel_owner_flutter/infrastructure/analytic/repositories/analytic_repository.dart'
|
||||||
|
as _i393;
|
||||||
import 'package:apskel_owner_flutter/infrastructure/auth/datasources/local_data_provider.dart'
|
import 'package:apskel_owner_flutter/infrastructure/auth/datasources/local_data_provider.dart'
|
||||||
as _i991;
|
as _i991;
|
||||||
import 'package:apskel_owner_flutter/infrastructure/auth/datasources/remote_data_provider.dart'
|
import 'package:apskel_owner_flutter/infrastructure/auth/datasources/remote_data_provider.dart'
|
||||||
@ -89,12 +97,21 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i17.AuthRemoteDataProvider>(
|
gh.factory<_i17.AuthRemoteDataProvider>(
|
||||||
() => _i17.AuthRemoteDataProvider(gh<_i115.ApiClient>()),
|
() => _i17.AuthRemoteDataProvider(gh<_i115.ApiClient>()),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i866.AnalyticRemoteDataProvider>(
|
||||||
|
() => _i866.AnalyticRemoteDataProvider(gh<_i115.ApiClient>()),
|
||||||
|
);
|
||||||
|
gh.factory<_i477.IAnalyticRepository>(
|
||||||
|
() => _i393.AnalyticRepository(gh<_i866.AnalyticRemoteDataProvider>()),
|
||||||
|
);
|
||||||
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<_i882.SalesLoaderBloc>(
|
||||||
|
() => _i882.SalesLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||||
|
);
|
||||||
gh.factory<_i775.LoginFormBloc>(
|
gh.factory<_i775.LoginFormBloc>(
|
||||||
() => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()),
|
() => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,72 +1,29 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../application/sales/sales_loader/sales_loader_bloc.dart';
|
||||||
|
import '../../../common/extension/extension.dart';
|
||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
|
import '../../../domain/analytic/analytic.dart';
|
||||||
|
import '../../../injection.dart';
|
||||||
import '../../components/appbar/appbar.dart';
|
import '../../components/appbar/appbar.dart';
|
||||||
import '../../components/spacer/spacer.dart';
|
import '../../components/spacer/spacer.dart';
|
||||||
import 'widgets/summary_card.dart';
|
import 'widgets/summary_card.dart';
|
||||||
|
|
||||||
// Data Models
|
|
||||||
class SalesData {
|
|
||||||
final String dateFrom;
|
|
||||||
final String dateTo;
|
|
||||||
final SalesSummary summary;
|
|
||||||
final List<DailySales> dailySales;
|
|
||||||
|
|
||||||
SalesData({
|
|
||||||
required this.dateFrom,
|
|
||||||
required this.dateTo,
|
|
||||||
required this.summary,
|
|
||||||
required this.dailySales,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SalesSummary {
|
|
||||||
final double totalSales;
|
|
||||||
final int totalOrders;
|
|
||||||
final int totalItems;
|
|
||||||
final double averageOrderValue;
|
|
||||||
final double totalTax;
|
|
||||||
final double totalDiscount;
|
|
||||||
final double netSales;
|
|
||||||
|
|
||||||
SalesSummary({
|
|
||||||
required this.totalSales,
|
|
||||||
required this.totalOrders,
|
|
||||||
required this.totalItems,
|
|
||||||
required this.averageOrderValue,
|
|
||||||
required this.totalTax,
|
|
||||||
required this.totalDiscount,
|
|
||||||
required this.netSales,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DailySales {
|
|
||||||
final DateTime date;
|
|
||||||
final double sales;
|
|
||||||
final int orders;
|
|
||||||
final int items;
|
|
||||||
final double tax;
|
|
||||||
final double discount;
|
|
||||||
final double netSales;
|
|
||||||
|
|
||||||
DailySales({
|
|
||||||
required this.date,
|
|
||||||
required this.sales,
|
|
||||||
required this.orders,
|
|
||||||
required this.items,
|
|
||||||
required this.tax,
|
|
||||||
required this.discount,
|
|
||||||
required this.netSales,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class SalesPage extends StatefulWidget {
|
class SalesPage extends StatefulWidget implements AutoRouteWrapper {
|
||||||
const SalesPage({super.key});
|
const SalesPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SalesPage> createState() => _SalesPageState();
|
State<SalesPage> createState() => _SalesPageState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||||
|
create: (context) =>
|
||||||
|
getIt<SalesLoaderBloc>()..add(SalesLoaderEvent.fectched()),
|
||||||
|
child: this,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||||
@ -115,50 +72,13 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sample data based on your JSON
|
|
||||||
final SalesData salesData = SalesData(
|
|
||||||
dateFrom: "2025-08-01T00:00:00+07:00",
|
|
||||||
dateTo: "2025-08-15T23:59:59.999999999+07:00",
|
|
||||||
summary: SalesSummary(
|
|
||||||
totalSales: 4291000,
|
|
||||||
totalOrders: 62,
|
|
||||||
totalItems: 62,
|
|
||||||
averageOrderValue: 69209.67741935483,
|
|
||||||
totalTax: 0,
|
|
||||||
totalDiscount: 0,
|
|
||||||
netSales: 4291000,
|
|
||||||
),
|
|
||||||
dailySales: [
|
|
||||||
DailySales(
|
|
||||||
date: DateTime.parse("2025-08-13T00:00:00Z"),
|
|
||||||
sales: 3841000,
|
|
||||||
orders: 52,
|
|
||||||
items: 52,
|
|
||||||
tax: 0,
|
|
||||||
discount: 0,
|
|
||||||
netSales: 3841000,
|
|
||||||
),
|
|
||||||
DailySales(
|
|
||||||
date: DateTime.parse("2025-08-14T00:00:00Z"),
|
|
||||||
sales: 450000,
|
|
||||||
orders: 10,
|
|
||||||
items: 10,
|
|
||||||
tax: 0,
|
|
||||||
discount: 0,
|
|
||||||
netSales: 450000,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
String formatCurrency(double amount) {
|
|
||||||
return 'Rp ${amount.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColor.background,
|
backgroundColor: AppColor.background,
|
||||||
body: CustomScrollView(
|
body: BlocBuilder<SalesLoaderBloc, SalesLoaderState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
// App Bar
|
// App Bar
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
@ -194,7 +114,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.date_range, color: AppColor.primary, size: 20),
|
Icon(
|
||||||
|
Icons.date_range,
|
||||||
|
color: AppColor.primary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
SpaceWidth(8),
|
SpaceWidth(8),
|
||||||
Text(
|
Text(
|
||||||
'Aug 1 - Aug 15, 2025',
|
'Aug 1 - Aug 15, 2025',
|
||||||
@ -241,9 +165,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildSummaryCard(
|
child: _buildSummaryCard(
|
||||||
'Total Sales',
|
'Total Sales',
|
||||||
formatCurrency(
|
state
|
||||||
salesData.summary.totalSales,
|
.sales
|
||||||
),
|
.summary
|
||||||
|
.totalSales
|
||||||
|
.currencyFormatRp,
|
||||||
Icons.trending_up,
|
Icons.trending_up,
|
||||||
AppColor.success,
|
AppColor.success,
|
||||||
0,
|
0,
|
||||||
@ -253,7 +179,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildSummaryCard(
|
child: _buildSummaryCard(
|
||||||
'Total Orders',
|
'Total Orders',
|
||||||
'${salesData.summary.totalOrders}',
|
state.sales.summary.totalOrders
|
||||||
|
.toString(),
|
||||||
Icons.shopping_cart,
|
Icons.shopping_cart,
|
||||||
AppColor.info,
|
AppColor.info,
|
||||||
100,
|
100,
|
||||||
@ -277,9 +204,9 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildSummaryCard(
|
child: _buildSummaryCard(
|
||||||
'Avg Order Value',
|
'Avg Order Value',
|
||||||
formatCurrency(
|
state.sales.summary.averageOrderValue
|
||||||
salesData.summary.averageOrderValue,
|
.round()
|
||||||
),
|
.currencyFormatRp,
|
||||||
Icons.attach_money,
|
Icons.attach_money,
|
||||||
AppColor.warning,
|
AppColor.warning,
|
||||||
200,
|
200,
|
||||||
@ -289,7 +216,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildSummaryCard(
|
child: _buildSummaryCard(
|
||||||
'Total Items',
|
'Total Items',
|
||||||
'${salesData.summary.totalItems}',
|
state.sales.summary.totalItems
|
||||||
|
.toString(),
|
||||||
Icons.inventory,
|
Icons.inventory,
|
||||||
AppColor.primary,
|
AppColor.primary,
|
||||||
300,
|
300,
|
||||||
@ -351,7 +279,9 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: Colors.white.withOpacity(0.2),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(
|
||||||
|
12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.account_balance_wallet,
|
Icons.account_balance_wallet,
|
||||||
@ -365,7 +295,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
SpaceWidth(16),
|
SpaceWidth(16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Net Sales',
|
'Net Sales',
|
||||||
@ -381,7 +312,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(
|
tween: Tween(
|
||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: salesData.summary.netSales,
|
end: state.sales.summary.netSales
|
||||||
|
.toDouble(),
|
||||||
),
|
),
|
||||||
duration: const Duration(
|
duration: const Duration(
|
||||||
milliseconds: 2000,
|
milliseconds: 2000,
|
||||||
@ -389,7 +321,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
builder: (context, countValue, child) {
|
builder: (context, countValue, child) {
|
||||||
return Text(
|
return Text(
|
||||||
formatCurrency(countValue),
|
state
|
||||||
|
.sales
|
||||||
|
.summary
|
||||||
|
.netSales
|
||||||
|
.currencyFormatRp,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColor.textWhite,
|
color: AppColor.textWhite,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
@ -435,7 +371,6 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
// Daily Sales List
|
// Daily Sales List
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
final dailySale = salesData.dailySales[index];
|
|
||||||
return SlideTransition(
|
return SlideTransition(
|
||||||
position:
|
position:
|
||||||
Tween<Offset>(
|
Tween<Offset>(
|
||||||
@ -478,16 +413,18 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: _buildDailySalesItem(dailySale),
|
child: _buildDailySalesItem(state.sales.data[index]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, childCount: salesData.dailySales.length),
|
}, childCount: state.sales.data.length),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Bottom Padding
|
// Bottom Padding
|
||||||
const SliverToBoxAdapter(child: SpaceHeight(32)),
|
const SliverToBoxAdapter(child: SpaceHeight(32)),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -509,7 +446,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDailySalesItem(DailySales dailySale) {
|
Widget _buildDailySalesItem(SalesAnalyticData dailySale) {
|
||||||
return ExpansionTile(
|
return ExpansionTile(
|
||||||
leading: Container(
|
leading: Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
@ -527,7 +464,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
formatCurrency(dailySale.sales),
|
dailySale.sales.currencyFormatRp,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColor.success,
|
color: AppColor.success,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -564,14 +501,14 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildDetailItem(
|
child: _buildDetailItem(
|
||||||
'Tax',
|
'Tax',
|
||||||
formatCurrency(dailySale.tax),
|
dailySale.tax.currencyFormatRp,
|
||||||
Icons.receipt,
|
Icons.receipt,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildDetailItem(
|
child: _buildDetailItem(
|
||||||
'Discount',
|
'Discount',
|
||||||
formatCurrency(dailySale.discount),
|
dailySale.discount.currencyFormatRp,
|
||||||
Icons.local_offer,
|
Icons.local_offer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -198,7 +198,7 @@ class ProfileRoute extends _i17.PageRouteInfo<void> {
|
|||||||
static _i17.PageInfo page = _i17.PageInfo(
|
static _i17.PageInfo page = _i17.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i10.ProfilePage();
|
return _i17.WrappedRoute(child: const _i10.ProfilePage());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -246,7 +246,7 @@ class SalesRoute extends _i17.PageRouteInfo<void> {
|
|||||||
static _i17.PageInfo page = _i17.PageInfo(
|
static _i17.PageInfo page = _i17.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i13.SalesPage();
|
return _i17.WrappedRoute(child: const _i13.SalesPage());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1045,6 +1045,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
shimmer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shimmer
|
||||||
|
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
simple_gesture_detector:
|
simple_gesture_detector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -40,6 +40,7 @@ dependencies:
|
|||||||
table_calendar: ^3.2.0
|
table_calendar: ^3.2.0
|
||||||
package_info_plus: ^8.3.1
|
package_info_plus: ^8.3.1
|
||||||
loader_overlay: ^5.0.0
|
loader_overlay: ^5.0.0
|
||||||
|
shimmer: ^3.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user