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
|
||||
static const String login = '/api/v1/auth/login';
|
||||
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;
|
||||
import 'package:apskel_owner_flutter/application/language/language_bloc.dart'
|
||||
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/di/di_auto_route.dart' as _i311;
|
||||
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;
|
||||
import 'package:apskel_owner_flutter/common/network/network_client.dart'
|
||||
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/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'
|
||||
as _i991;
|
||||
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>(
|
||||
() => _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>(
|
||||
() => _i1035.AuthRepository(
|
||||
gh<_i991.AuthLocalDataProvider>(),
|
||||
gh<_i17.AuthRemoteDataProvider>(),
|
||||
),
|
||||
);
|
||||
gh.factory<_i882.SalesLoaderBloc>(
|
||||
() => _i882.SalesLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||
);
|
||||
gh.factory<_i775.LoginFormBloc>(
|
||||
() => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()),
|
||||
);
|
||||
|
||||
@ -1,72 +1,29 @@
|
||||
import 'package:auto_route/auto_route.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 '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import '../../components/spacer/spacer.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()
|
||||
class SalesPage extends StatefulWidget {
|
||||
class SalesPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
const SalesPage({super.key});
|
||||
|
||||
@override
|
||||
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 {
|
||||
@ -115,50 +72,13 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body: CustomScrollView(
|
||||
body: BlocBuilder<SalesLoaderBloc, SalesLoaderState>(
|
||||
builder: (context, state) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// App Bar
|
||||
SliverAppBar(
|
||||
@ -194,7 +114,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.date_range, color: AppColor.primary, size: 20),
|
||||
Icon(
|
||||
Icons.date_range,
|
||||
color: AppColor.primary,
|
||||
size: 20,
|
||||
),
|
||||
SpaceWidth(8),
|
||||
Text(
|
||||
'Aug 1 - Aug 15, 2025',
|
||||
@ -241,9 +165,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Sales',
|
||||
formatCurrency(
|
||||
salesData.summary.totalSales,
|
||||
),
|
||||
state
|
||||
.sales
|
||||
.summary
|
||||
.totalSales
|
||||
.currencyFormatRp,
|
||||
Icons.trending_up,
|
||||
AppColor.success,
|
||||
0,
|
||||
@ -253,7 +179,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Orders',
|
||||
'${salesData.summary.totalOrders}',
|
||||
state.sales.summary.totalOrders
|
||||
.toString(),
|
||||
Icons.shopping_cart,
|
||||
AppColor.info,
|
||||
100,
|
||||
@ -277,9 +204,9 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Avg Order Value',
|
||||
formatCurrency(
|
||||
salesData.summary.averageOrderValue,
|
||||
),
|
||||
state.sales.summary.averageOrderValue
|
||||
.round()
|
||||
.currencyFormatRp,
|
||||
Icons.attach_money,
|
||||
AppColor.warning,
|
||||
200,
|
||||
@ -289,7 +216,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Items',
|
||||
'${salesData.summary.totalItems}',
|
||||
state.sales.summary.totalItems
|
||||
.toString(),
|
||||
Icons.inventory,
|
||||
AppColor.primary,
|
||||
300,
|
||||
@ -351,7 +279,9 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.account_balance_wallet,
|
||||
@ -365,7 +295,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
SpaceWidth(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Net Sales',
|
||||
@ -381,7 +312,8 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(
|
||||
begin: 0.0,
|
||||
end: salesData.summary.netSales,
|
||||
end: state.sales.summary.netSales
|
||||
.toDouble(),
|
||||
),
|
||||
duration: const Duration(
|
||||
milliseconds: 2000,
|
||||
@ -389,7 +321,11 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (context, countValue, child) {
|
||||
return Text(
|
||||
formatCurrency(countValue),
|
||||
state
|
||||
.sales
|
||||
.summary
|
||||
.netSales
|
||||
.currencyFormatRp,
|
||||
style: const TextStyle(
|
||||
color: AppColor.textWhite,
|
||||
fontSize: 24,
|
||||
@ -435,7 +371,6 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
// Daily Sales List
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final dailySale = salesData.dailySales[index];
|
||||
return SlideTransition(
|
||||
position:
|
||||
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
|
||||
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(
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
@ -527,7 +464,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
formatCurrency(dailySale.sales),
|
||||
dailySale.sales.currencyFormatRp,
|
||||
style: TextStyle(
|
||||
color: AppColor.success,
|
||||
fontWeight: FontWeight.w600,
|
||||
@ -564,14 +501,14 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildDetailItem(
|
||||
'Tax',
|
||||
formatCurrency(dailySale.tax),
|
||||
dailySale.tax.currencyFormatRp,
|
||||
Icons.receipt,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildDetailItem(
|
||||
'Discount',
|
||||
formatCurrency(dailySale.discount),
|
||||
dailySale.discount.currencyFormatRp,
|
||||
Icons.local_offer,
|
||||
),
|
||||
),
|
||||
|
||||
@ -198,7 +198,7 @@ class ProfileRoute extends _i17.PageRouteInfo<void> {
|
||||
static _i17.PageInfo page = _i17.PageInfo(
|
||||
name,
|
||||
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(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i13.SalesPage();
|
||||
return _i17.WrappedRoute(child: const _i13.SalesPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -1045,6 +1045,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -40,6 +40,7 @@ dependencies:
|
||||
table_calendar: ^3.2.0
|
||||
package_info_plus: ^8.3.1
|
||||
loader_overlay: ^5.0.0
|
||||
shimmer: ^3.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user