report product

This commit is contained in:
efrilm 2025-11-03 19:34:46 +07:00
parent 3bb6bd653e
commit 14c3c69ad6
19 changed files with 2557 additions and 2 deletions

View File

@ -0,0 +1,50 @@
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';
part 'product_analytic_loader_event.dart';
part 'product_analytic_loader_state.dart';
part 'product_analytic_loader_bloc.freezed.dart';
@injectable
class ProductAnalyticLoaderBloc
extends Bloc<ProductAnalyticLoaderEvent, ProductAnalyticLoaderState> {
final IAnalyticRepository _analyticRepository;
ProductAnalyticLoaderBloc(this._analyticRepository)
: super(ProductAnalyticLoaderState.initial()) {
on<ProductAnalyticLoaderEvent>(_onProductAnalyticLoaderEvent);
}
Future<void> _onProductAnalyticLoaderEvent(
ProductAnalyticLoaderEvent event,
Emitter<ProductAnalyticLoaderState> emit,
) {
return event.map(
fetched: (e) async {
emit(state.copyWith(isFetching: true, failureOption: none()));
final result = await _analyticRepository.getProducts(
dateFrom: e.startDate,
dateTo: e.endDate,
);
await result.fold(
(failure) async {
emit(
state.copyWith(
isFetching: false,
failureOption: optionOf(failure),
),
);
},
(products) async {
emit(state.copyWith(isFetching: false, productAnalytic: products));
},
);
},
);
}
}

View File

@ -0,0 +1,475 @@
// 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 'product_analytic_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 _$ProductAnalyticLoaderEvent {
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticLoaderEventCopyWith<ProductAnalyticLoaderEvent>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticLoaderEventCopyWith<$Res> {
factory $ProductAnalyticLoaderEventCopyWith(
ProductAnalyticLoaderEvent value,
$Res Function(ProductAnalyticLoaderEvent) then,
) =
_$ProductAnalyticLoaderEventCopyWithImpl<
$Res,
ProductAnalyticLoaderEvent
>;
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class _$ProductAnalyticLoaderEventCopyWithImpl<
$Res,
$Val extends ProductAnalyticLoaderEvent
>
implements $ProductAnalyticLoaderEventCopyWith<$Res> {
_$ProductAnalyticLoaderEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? startDate = null, Object? endDate = null}) {
return _then(
_value.copyWith(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$FetchedImplCopyWith<$Res>
implements $ProductAnalyticLoaderEventCopyWith<$Res> {
factory _$$FetchedImplCopyWith(
_$FetchedImpl value,
$Res Function(_$FetchedImpl) then,
) = __$$FetchedImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class __$$FetchedImplCopyWithImpl<$Res>
extends _$ProductAnalyticLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
implements _$$FetchedImplCopyWith<$Res> {
__$$FetchedImplCopyWithImpl(
_$FetchedImpl _value,
$Res Function(_$FetchedImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? startDate = null, Object? endDate = null}) {
return _then(
_$FetchedImpl(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
),
);
}
}
/// @nodoc
class _$FetchedImpl implements _Fetched {
const _$FetchedImpl({required this.startDate, required this.endDate});
@override
final DateTime startDate;
@override
final DateTime endDate;
@override
String toString() {
return 'ProductAnalyticLoaderEvent.fetched(startDate: $startDate, endDate: $endDate)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FetchedImpl &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate));
}
@override
int get hashCode => Object.hash(runtimeType, startDate, endDate);
/// Create a copy of ProductAnalyticLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
__$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) fetched,
}) {
return fetched(startDate, endDate);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? fetched,
}) {
return fetched?.call(startDate, endDate);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(startDate, endDate);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetched value) fetched,
}) {
return fetched(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetched value)? fetched,
}) {
return fetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetched value)? fetched,
required TResult orElse(),
}) {
if (fetched != null) {
return fetched(this);
}
return orElse();
}
}
abstract class _Fetched implements ProductAnalyticLoaderEvent {
const factory _Fetched({
required final DateTime startDate,
required final DateTime endDate,
}) = _$FetchedImpl;
@override
DateTime get startDate;
@override
DateTime get endDate;
/// Create a copy of ProductAnalyticLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProductAnalyticLoaderState {
ProductAnalytic get productAnalytic => throw _privateConstructorUsedError;
Option<AnalyticFailure> get failureOption =>
throw _privateConstructorUsedError;
bool get isFetching => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticLoaderStateCopyWith<ProductAnalyticLoaderState>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticLoaderStateCopyWith<$Res> {
factory $ProductAnalyticLoaderStateCopyWith(
ProductAnalyticLoaderState value,
$Res Function(ProductAnalyticLoaderState) then,
) =
_$ProductAnalyticLoaderStateCopyWithImpl<
$Res,
ProductAnalyticLoaderState
>;
@useResult
$Res call({
ProductAnalytic productAnalytic,
Option<AnalyticFailure> failureOption,
bool isFetching,
});
$ProductAnalyticCopyWith<$Res> get productAnalytic;
}
/// @nodoc
class _$ProductAnalyticLoaderStateCopyWithImpl<
$Res,
$Val extends ProductAnalyticLoaderState
>
implements $ProductAnalyticLoaderStateCopyWith<$Res> {
_$ProductAnalyticLoaderStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productAnalytic = null,
Object? failureOption = null,
Object? isFetching = null,
}) {
return _then(
_value.copyWith(
productAnalytic: null == productAnalytic
? _value.productAnalytic
: productAnalytic // ignore: cast_nullable_to_non_nullable
as ProductAnalytic,
failureOption: null == failureOption
? _value.failureOption
: failureOption // 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 ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProductAnalyticCopyWith<$Res> get productAnalytic {
return $ProductAnalyticCopyWith<$Res>(_value.productAnalytic, (value) {
return _then(_value.copyWith(productAnalytic: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ProductAnalyticLoaderStateImplCopyWith<$Res>
implements $ProductAnalyticLoaderStateCopyWith<$Res> {
factory _$$ProductAnalyticLoaderStateImplCopyWith(
_$ProductAnalyticLoaderStateImpl value,
$Res Function(_$ProductAnalyticLoaderStateImpl) then,
) = __$$ProductAnalyticLoaderStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
ProductAnalytic productAnalytic,
Option<AnalyticFailure> failureOption,
bool isFetching,
});
@override
$ProductAnalyticCopyWith<$Res> get productAnalytic;
}
/// @nodoc
class __$$ProductAnalyticLoaderStateImplCopyWithImpl<$Res>
extends
_$ProductAnalyticLoaderStateCopyWithImpl<
$Res,
_$ProductAnalyticLoaderStateImpl
>
implements _$$ProductAnalyticLoaderStateImplCopyWith<$Res> {
__$$ProductAnalyticLoaderStateImplCopyWithImpl(
_$ProductAnalyticLoaderStateImpl _value,
$Res Function(_$ProductAnalyticLoaderStateImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productAnalytic = null,
Object? failureOption = null,
Object? isFetching = null,
}) {
return _then(
_$ProductAnalyticLoaderStateImpl(
productAnalytic: null == productAnalytic
? _value.productAnalytic
: productAnalytic // ignore: cast_nullable_to_non_nullable
as ProductAnalytic,
failureOption: null == failureOption
? _value.failureOption
: failureOption // 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 _$ProductAnalyticLoaderStateImpl implements _ProductAnalyticLoaderState {
_$ProductAnalyticLoaderStateImpl({
required this.productAnalytic,
required this.failureOption,
this.isFetching = false,
});
@override
final ProductAnalytic productAnalytic;
@override
final Option<AnalyticFailure> failureOption;
@override
@JsonKey()
final bool isFetching;
@override
String toString() {
return 'ProductAnalyticLoaderState(productAnalytic: $productAnalytic, failureOption: $failureOption, isFetching: $isFetching)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticLoaderStateImpl &&
(identical(other.productAnalytic, productAnalytic) ||
other.productAnalytic == productAnalytic) &&
(identical(other.failureOption, failureOption) ||
other.failureOption == failureOption) &&
(identical(other.isFetching, isFetching) ||
other.isFetching == isFetching));
}
@override
int get hashCode =>
Object.hash(runtimeType, productAnalytic, failureOption, isFetching);
/// Create a copy of ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticLoaderStateImplCopyWith<_$ProductAnalyticLoaderStateImpl>
get copyWith =>
__$$ProductAnalyticLoaderStateImplCopyWithImpl<
_$ProductAnalyticLoaderStateImpl
>(this, _$identity);
}
abstract class _ProductAnalyticLoaderState
implements ProductAnalyticLoaderState {
factory _ProductAnalyticLoaderState({
required final ProductAnalytic productAnalytic,
required final Option<AnalyticFailure> failureOption,
final bool isFetching,
}) = _$ProductAnalyticLoaderStateImpl;
@override
ProductAnalytic get productAnalytic;
@override
Option<AnalyticFailure> get failureOption;
@override
bool get isFetching;
/// Create a copy of ProductAnalyticLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticLoaderStateImplCopyWith<_$ProductAnalyticLoaderStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,9 @@
part of 'product_analytic_loader_bloc.dart';
@freezed
class ProductAnalyticLoaderEvent with _$ProductAnalyticLoaderEvent {
const factory ProductAnalyticLoaderEvent.fetched({
required DateTime startDate,
required DateTime endDate,
}) = _Fetched;
}

View File

@ -0,0 +1,15 @@
part of 'product_analytic_loader_bloc.dart';
@freezed
class ProductAnalyticLoaderState with _$ProductAnalyticLoaderState {
factory ProductAnalyticLoaderState({
required ProductAnalytic productAnalytic,
required Option<AnalyticFailure> failureOption,
@Default(false) bool isFetching,
}) = _ProductAnalyticLoaderState;
factory ProductAnalyticLoaderState.initial() => ProductAnalyticLoaderState(
productAnalytic: ProductAnalytic.empty(),
failureOption: none(),
);
}

View File

@ -10,4 +10,5 @@ class ApiPath {
static const String payments = '/api/v1/payments'; static const String payments = '/api/v1/payments';
static const String analyticDashboard = '/api/v1/analytics/dashboard'; static const String analyticDashboard = '/api/v1/analytics/dashboard';
static const String analyticSales = '/api/v1/analytics/sales'; static const String analyticSales = '/api/v1/analytics/sales';
static const String analyticProducts = '/api/v1/analytics/products';
} }

View File

@ -7,5 +7,6 @@ part 'analytic.freezed.dart';
part 'entities/dashboard_entity.dart'; part 'entities/dashboard_entity.dart';
part 'entities/sales_entity.dart'; part 'entities/sales_entity.dart';
part 'entities/product_analytic_entity.dart';
part 'failures/analytic_failure.dart'; part 'failures/analytic_failure.dart';
part 'repositories/i_analytic_repository.dart'; part 'repositories/i_analytic_repository.dart';

View File

@ -2349,6 +2349,750 @@ abstract class _SalesAnalyticItem implements SalesAnalyticItem {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
/// @nodoc
mixin _$ProductAnalytic {
String get organizationId => throw _privateConstructorUsedError;
String get outletId => throw _privateConstructorUsedError;
DateTime get dateFrom => throw _privateConstructorUsedError;
DateTime get dateTo => throw _privateConstructorUsedError;
List<ProductAnalyticItem> get data => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalytic
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticCopyWith<ProductAnalytic> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticCopyWith<$Res> {
factory $ProductAnalyticCopyWith(
ProductAnalytic value,
$Res Function(ProductAnalytic) then,
) = _$ProductAnalyticCopyWithImpl<$Res, ProductAnalytic>;
@useResult
$Res call({
String organizationId,
String outletId,
DateTime dateFrom,
DateTime dateTo,
List<ProductAnalyticItem> data,
});
}
/// @nodoc
class _$ProductAnalyticCopyWithImpl<$Res, $Val extends ProductAnalytic>
implements $ProductAnalyticCopyWith<$Res> {
_$ProductAnalyticCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalytic
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? organizationId = null,
Object? outletId = null,
Object? dateFrom = null,
Object? dateTo = null,
Object? data = null,
}) {
return _then(
_value.copyWith(
organizationId: null == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String,
outletId: null == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String,
dateFrom: null == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
as DateTime,
dateTo: null == dateTo
? _value.dateTo
: dateTo // ignore: cast_nullable_to_non_nullable
as DateTime,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as List<ProductAnalyticItem>,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$ProductAnalyticImplCopyWith<$Res>
implements $ProductAnalyticCopyWith<$Res> {
factory _$$ProductAnalyticImplCopyWith(
_$ProductAnalyticImpl value,
$Res Function(_$ProductAnalyticImpl) then,
) = __$$ProductAnalyticImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String organizationId,
String outletId,
DateTime dateFrom,
DateTime dateTo,
List<ProductAnalyticItem> data,
});
}
/// @nodoc
class __$$ProductAnalyticImplCopyWithImpl<$Res>
extends _$ProductAnalyticCopyWithImpl<$Res, _$ProductAnalyticImpl>
implements _$$ProductAnalyticImplCopyWith<$Res> {
__$$ProductAnalyticImplCopyWithImpl(
_$ProductAnalyticImpl _value,
$Res Function(_$ProductAnalyticImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalytic
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? organizationId = null,
Object? outletId = null,
Object? dateFrom = null,
Object? dateTo = null,
Object? data = null,
}) {
return _then(
_$ProductAnalyticImpl(
organizationId: null == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String,
outletId: null == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String,
dateFrom: null == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
as DateTime,
dateTo: null == dateTo
? _value.dateTo
: dateTo // ignore: cast_nullable_to_non_nullable
as DateTime,
data: null == data
? _value._data
: data // ignore: cast_nullable_to_non_nullable
as List<ProductAnalyticItem>,
),
);
}
}
/// @nodoc
class _$ProductAnalyticImpl extends _ProductAnalytic {
const _$ProductAnalyticImpl({
required this.organizationId,
required this.outletId,
required this.dateFrom,
required this.dateTo,
required final List<ProductAnalyticItem> data,
}) : _data = data,
super._();
@override
final String organizationId;
@override
final String outletId;
@override
final DateTime dateFrom;
@override
final DateTime dateTo;
final List<ProductAnalyticItem> _data;
@override
List<ProductAnalyticItem> get data {
if (_data is EqualUnmodifiableListView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_data);
}
@override
String toString() {
return 'ProductAnalytic(organizationId: $organizationId, outletId: $outletId, dateFrom: $dateFrom, dateTo: $dateTo, data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticImpl &&
(identical(other.organizationId, organizationId) ||
other.organizationId == organizationId) &&
(identical(other.outletId, outletId) ||
other.outletId == outletId) &&
(identical(other.dateFrom, dateFrom) ||
other.dateFrom == dateFrom) &&
(identical(other.dateTo, dateTo) || other.dateTo == dateTo) &&
const DeepCollectionEquality().equals(other._data, _data));
}
@override
int get hashCode => Object.hash(
runtimeType,
organizationId,
outletId,
dateFrom,
dateTo,
const DeepCollectionEquality().hash(_data),
);
/// Create a copy of ProductAnalytic
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticImplCopyWith<_$ProductAnalyticImpl> get copyWith =>
__$$ProductAnalyticImplCopyWithImpl<_$ProductAnalyticImpl>(
this,
_$identity,
);
}
abstract class _ProductAnalytic extends ProductAnalytic {
const factory _ProductAnalytic({
required final String organizationId,
required final String outletId,
required final DateTime dateFrom,
required final DateTime dateTo,
required final List<ProductAnalyticItem> data,
}) = _$ProductAnalyticImpl;
const _ProductAnalytic._() : super._();
@override
String get organizationId;
@override
String get outletId;
@override
DateTime get dateFrom;
@override
DateTime get dateTo;
@override
List<ProductAnalyticItem> get data;
/// Create a copy of ProductAnalytic
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticImplCopyWith<_$ProductAnalyticImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProductAnalyticItem {
String get productId => throw _privateConstructorUsedError;
String get productName => throw _privateConstructorUsedError;
String get categoryId => throw _privateConstructorUsedError;
String get categoryName => throw _privateConstructorUsedError;
int get quantitySold => throw _privateConstructorUsedError;
int get revenue => throw _privateConstructorUsedError;
double get averagePrice => throw _privateConstructorUsedError;
int get orderCount => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticItemCopyWith<ProductAnalyticItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticItemCopyWith<$Res> {
factory $ProductAnalyticItemCopyWith(
ProductAnalyticItem value,
$Res Function(ProductAnalyticItem) then,
) = _$ProductAnalyticItemCopyWithImpl<$Res, ProductAnalyticItem>;
@useResult
$Res call({
String productId,
String productName,
String categoryId,
String categoryName,
int quantitySold,
int revenue,
double averagePrice,
int orderCount,
});
}
/// @nodoc
class _$ProductAnalyticItemCopyWithImpl<$Res, $Val extends ProductAnalyticItem>
implements $ProductAnalyticItemCopyWith<$Res> {
_$ProductAnalyticItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productId = null,
Object? productName = null,
Object? categoryId = null,
Object? categoryName = null,
Object? quantitySold = null,
Object? revenue = null,
Object? averagePrice = null,
Object? orderCount = null,
}) {
return _then(
_value.copyWith(
productId: null == productId
? _value.productId
: productId // ignore: cast_nullable_to_non_nullable
as String,
productName: null == productName
? _value.productName
: productName // ignore: cast_nullable_to_non_nullable
as String,
categoryId: null == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String,
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
quantitySold: null == quantitySold
? _value.quantitySold
: quantitySold // ignore: cast_nullable_to_non_nullable
as int,
revenue: null == revenue
? _value.revenue
: revenue // ignore: cast_nullable_to_non_nullable
as int,
averagePrice: null == averagePrice
? _value.averagePrice
: averagePrice // ignore: cast_nullable_to_non_nullable
as double,
orderCount: null == orderCount
? _value.orderCount
: orderCount // ignore: cast_nullable_to_non_nullable
as int,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$ProductAnalyticItemImplCopyWith<$Res>
implements $ProductAnalyticItemCopyWith<$Res> {
factory _$$ProductAnalyticItemImplCopyWith(
_$ProductAnalyticItemImpl value,
$Res Function(_$ProductAnalyticItemImpl) then,
) = __$$ProductAnalyticItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String productId,
String productName,
String categoryId,
String categoryName,
int quantitySold,
int revenue,
double averagePrice,
int orderCount,
});
}
/// @nodoc
class __$$ProductAnalyticItemImplCopyWithImpl<$Res>
extends _$ProductAnalyticItemCopyWithImpl<$Res, _$ProductAnalyticItemImpl>
implements _$$ProductAnalyticItemImplCopyWith<$Res> {
__$$ProductAnalyticItemImplCopyWithImpl(
_$ProductAnalyticItemImpl _value,
$Res Function(_$ProductAnalyticItemImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productId = null,
Object? productName = null,
Object? categoryId = null,
Object? categoryName = null,
Object? quantitySold = null,
Object? revenue = null,
Object? averagePrice = null,
Object? orderCount = null,
}) {
return _then(
_$ProductAnalyticItemImpl(
productId: null == productId
? _value.productId
: productId // ignore: cast_nullable_to_non_nullable
as String,
productName: null == productName
? _value.productName
: productName // ignore: cast_nullable_to_non_nullable
as String,
categoryId: null == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String,
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
quantitySold: null == quantitySold
? _value.quantitySold
: quantitySold // ignore: cast_nullable_to_non_nullable
as int,
revenue: null == revenue
? _value.revenue
: revenue // ignore: cast_nullable_to_non_nullable
as int,
averagePrice: null == averagePrice
? _value.averagePrice
: averagePrice // ignore: cast_nullable_to_non_nullable
as double,
orderCount: null == orderCount
? _value.orderCount
: orderCount // ignore: cast_nullable_to_non_nullable
as int,
),
);
}
}
/// @nodoc
class _$ProductAnalyticItemImpl implements _ProductAnalyticItem {
const _$ProductAnalyticItemImpl({
required this.productId,
required this.productName,
required this.categoryId,
required this.categoryName,
required this.quantitySold,
required this.revenue,
required this.averagePrice,
required this.orderCount,
});
@override
final String productId;
@override
final String productName;
@override
final String categoryId;
@override
final String categoryName;
@override
final int quantitySold;
@override
final int revenue;
@override
final double averagePrice;
@override
final int orderCount;
@override
String toString() {
return 'ProductAnalyticItem(productId: $productId, productName: $productName, categoryId: $categoryId, categoryName: $categoryName, quantitySold: $quantitySold, revenue: $revenue, averagePrice: $averagePrice, orderCount: $orderCount)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticItemImpl &&
(identical(other.productId, productId) ||
other.productId == productId) &&
(identical(other.productName, productName) ||
other.productName == productName) &&
(identical(other.categoryId, categoryId) ||
other.categoryId == categoryId) &&
(identical(other.categoryName, categoryName) ||
other.categoryName == categoryName) &&
(identical(other.quantitySold, quantitySold) ||
other.quantitySold == quantitySold) &&
(identical(other.revenue, revenue) || other.revenue == revenue) &&
(identical(other.averagePrice, averagePrice) ||
other.averagePrice == averagePrice) &&
(identical(other.orderCount, orderCount) ||
other.orderCount == orderCount));
}
@override
int get hashCode => Object.hash(
runtimeType,
productId,
productName,
categoryId,
categoryName,
quantitySold,
revenue,
averagePrice,
orderCount,
);
/// Create a copy of ProductAnalyticItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticItemImplCopyWith<_$ProductAnalyticItemImpl> get copyWith =>
__$$ProductAnalyticItemImplCopyWithImpl<_$ProductAnalyticItemImpl>(
this,
_$identity,
);
}
abstract class _ProductAnalyticItem implements ProductAnalyticItem {
const factory _ProductAnalyticItem({
required final String productId,
required final String productName,
required final String categoryId,
required final String categoryName,
required final int quantitySold,
required final int revenue,
required final double averagePrice,
required final int orderCount,
}) = _$ProductAnalyticItemImpl;
@override
String get productId;
@override
String get productName;
@override
String get categoryId;
@override
String get categoryName;
@override
int get quantitySold;
@override
int get revenue;
@override
double get averagePrice;
@override
int get orderCount;
/// Create a copy of ProductAnalyticItem
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticItemImplCopyWith<_$ProductAnalyticItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProductAnalyticCategoryItem {
String get categoryName => throw _privateConstructorUsedError;
int get productCount => throw _privateConstructorUsedError;
int get totalRevenue => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticCategoryItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticCategoryItemCopyWith<ProductAnalyticCategoryItem>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticCategoryItemCopyWith<$Res> {
factory $ProductAnalyticCategoryItemCopyWith(
ProductAnalyticCategoryItem value,
$Res Function(ProductAnalyticCategoryItem) then,
) =
_$ProductAnalyticCategoryItemCopyWithImpl<
$Res,
ProductAnalyticCategoryItem
>;
@useResult
$Res call({String categoryName, int productCount, int totalRevenue});
}
/// @nodoc
class _$ProductAnalyticCategoryItemCopyWithImpl<
$Res,
$Val extends ProductAnalyticCategoryItem
>
implements $ProductAnalyticCategoryItemCopyWith<$Res> {
_$ProductAnalyticCategoryItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticCategoryItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? categoryName = null,
Object? productCount = null,
Object? totalRevenue = null,
}) {
return _then(
_value.copyWith(
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
productCount: null == productCount
? _value.productCount
: productCount // ignore: cast_nullable_to_non_nullable
as int,
totalRevenue: null == totalRevenue
? _value.totalRevenue
: totalRevenue // ignore: cast_nullable_to_non_nullable
as int,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$ProductAnalyticCategoryItemImplCopyWith<$Res>
implements $ProductAnalyticCategoryItemCopyWith<$Res> {
factory _$$ProductAnalyticCategoryItemImplCopyWith(
_$ProductAnalyticCategoryItemImpl value,
$Res Function(_$ProductAnalyticCategoryItemImpl) then,
) = __$$ProductAnalyticCategoryItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String categoryName, int productCount, int totalRevenue});
}
/// @nodoc
class __$$ProductAnalyticCategoryItemImplCopyWithImpl<$Res>
extends
_$ProductAnalyticCategoryItemCopyWithImpl<
$Res,
_$ProductAnalyticCategoryItemImpl
>
implements _$$ProductAnalyticCategoryItemImplCopyWith<$Res> {
__$$ProductAnalyticCategoryItemImplCopyWithImpl(
_$ProductAnalyticCategoryItemImpl _value,
$Res Function(_$ProductAnalyticCategoryItemImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticCategoryItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? categoryName = null,
Object? productCount = null,
Object? totalRevenue = null,
}) {
return _then(
_$ProductAnalyticCategoryItemImpl(
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
productCount: null == productCount
? _value.productCount
: productCount // ignore: cast_nullable_to_non_nullable
as int,
totalRevenue: null == totalRevenue
? _value.totalRevenue
: totalRevenue // ignore: cast_nullable_to_non_nullable
as int,
),
);
}
}
/// @nodoc
class _$ProductAnalyticCategoryItemImpl
implements _ProductAnalyticCategoryItem {
const _$ProductAnalyticCategoryItemImpl({
required this.categoryName,
required this.productCount,
required this.totalRevenue,
});
@override
final String categoryName;
@override
final int productCount;
@override
final int totalRevenue;
@override
String toString() {
return 'ProductAnalyticCategoryItem(categoryName: $categoryName, productCount: $productCount, totalRevenue: $totalRevenue)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticCategoryItemImpl &&
(identical(other.categoryName, categoryName) ||
other.categoryName == categoryName) &&
(identical(other.productCount, productCount) ||
other.productCount == productCount) &&
(identical(other.totalRevenue, totalRevenue) ||
other.totalRevenue == totalRevenue));
}
@override
int get hashCode =>
Object.hash(runtimeType, categoryName, productCount, totalRevenue);
/// Create a copy of ProductAnalyticCategoryItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticCategoryItemImplCopyWith<_$ProductAnalyticCategoryItemImpl>
get copyWith =>
__$$ProductAnalyticCategoryItemImplCopyWithImpl<
_$ProductAnalyticCategoryItemImpl
>(this, _$identity);
}
abstract class _ProductAnalyticCategoryItem
implements ProductAnalyticCategoryItem {
const factory _ProductAnalyticCategoryItem({
required final String categoryName,
required final int productCount,
required final int totalRevenue,
}) = _$ProductAnalyticCategoryItemImpl;
@override
String get categoryName;
@override
int get productCount;
@override
int get totalRevenue;
/// Create a copy of ProductAnalyticCategoryItem
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticCategoryItemImplCopyWith<_$ProductAnalyticCategoryItemImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc /// @nodoc
mixin _$AnalyticFailure { mixin _$AnalyticFailure {
@optionalTypeArgs @optionalTypeArgs

View File

@ -0,0 +1,97 @@
part of '../analytic.dart';
@freezed
class ProductAnalytic with _$ProductAnalytic {
const ProductAnalytic._();
const factory ProductAnalytic({
required String organizationId,
required String outletId,
required DateTime dateFrom,
required DateTime dateTo,
required List<ProductAnalyticItem> data,
}) = _ProductAnalytic;
factory ProductAnalytic.empty() => ProductAnalytic(
organizationId: '',
outletId: '',
dateFrom: DateTime.now(),
dateTo: DateTime.now(),
data: const [],
);
ProductAnalyticItem get bestProduct {
if (data.isEmpty) {
return ProductAnalyticItem.empty();
}
return data.first;
}
List<ProductAnalyticCategoryItem> get categories {
final Map<String, ProductAnalyticCategoryItem> categoryMap = {};
for (final product in data) {
final key = product.categoryName;
if (categoryMap.containsKey(key)) {
final existing = categoryMap[key]!;
categoryMap[key] = existing.copyWith(
productCount: existing.productCount + 1,
totalRevenue: existing.totalRevenue + product.revenue,
);
} else {
categoryMap[key] = ProductAnalyticCategoryItem(
categoryName: product.categoryName,
productCount: 1,
totalRevenue: product.revenue,
);
}
}
final categorySummary = categoryMap.values.toList()
..sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
return categorySummary;
}
}
@freezed
class ProductAnalyticItem with _$ProductAnalyticItem {
const factory ProductAnalyticItem({
required String productId,
required String productName,
required String categoryId,
required String categoryName,
required int quantitySold,
required int revenue,
required double averagePrice,
required int orderCount,
}) = _ProductAnalyticItem;
factory ProductAnalyticItem.empty() => const ProductAnalyticItem(
productId: '',
productName: '',
categoryId: '',
categoryName: '',
quantitySold: 0,
revenue: 0,
averagePrice: 0.0,
orderCount: 0,
);
}
@freezed
class ProductAnalyticCategoryItem with _$ProductAnalyticCategoryItem {
const factory ProductAnalyticCategoryItem({
required String categoryName,
required int productCount,
required int totalRevenue,
}) = _ProductAnalyticCategoryItem;
factory ProductAnalyticCategoryItem.empty() => ProductAnalyticCategoryItem(
categoryName: '',
productCount: 0,
totalRevenue: 0,
);
}

View File

@ -9,4 +9,8 @@ abstract class IAnalyticRepository {
required DateTime dateFrom, required DateTime dateFrom,
required DateTime dateTo, required DateTime dateTo,
}); });
Future<Either<AnalyticFailure, ProductAnalytic>> getProducts({
required DateTime dateFrom,
required DateTime dateTo,
});
} }

View File

@ -7,3 +7,4 @@ part 'analytic_dtos.g.dart';
part 'dtos/dashboard_dto.dart'; part 'dtos/dashboard_dto.dart';
part 'dtos/sales_dto.dart'; part 'dtos/sales_dto.dart';
part 'dtos/product_analytic_dto.dart';

View File

@ -2739,3 +2739,638 @@ abstract class _SalesAnalyticItemDto extends SalesAnalyticItemDto {
_$$SalesAnalyticItemDtoImplCopyWith<_$SalesAnalyticItemDtoImpl> _$$SalesAnalyticItemDtoImplCopyWith<_$SalesAnalyticItemDtoImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
ProductAnalyticDto _$ProductAnalyticDtoFromJson(Map<String, dynamic> json) {
return _ProductAnalyticDto.fromJson(json);
}
/// @nodoc
mixin _$ProductAnalyticDto {
@JsonKey(name: "organization_id")
String? get organizationId => throw _privateConstructorUsedError;
@JsonKey(name: "outlet_id")
String? get outletId => throw _privateConstructorUsedError;
@JsonKey(name: "date_from")
DateTime? get dateFrom => throw _privateConstructorUsedError;
@JsonKey(name: "date_to")
DateTime? get dateTo => throw _privateConstructorUsedError;
@JsonKey(name: "data")
List<ProductAnalyticItemDto>? get data => throw _privateConstructorUsedError;
/// Serializes this ProductAnalyticDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticDtoCopyWith<ProductAnalyticDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticDtoCopyWith<$Res> {
factory $ProductAnalyticDtoCopyWith(
ProductAnalyticDto value,
$Res Function(ProductAnalyticDto) then,
) = _$ProductAnalyticDtoCopyWithImpl<$Res, ProductAnalyticDto>;
@useResult
$Res call({
@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: "data") List<ProductAnalyticItemDto>? data,
});
}
/// @nodoc
class _$ProductAnalyticDtoCopyWithImpl<$Res, $Val extends ProductAnalyticDto>
implements $ProductAnalyticDtoCopyWith<$Res> {
_$ProductAnalyticDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? organizationId = freezed,
Object? outletId = freezed,
Object? dateFrom = freezed,
Object? dateTo = freezed,
Object? data = freezed,
}) {
return _then(
_value.copyWith(
organizationId: freezed == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String?,
outletId: freezed == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String?,
dateFrom: freezed == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
as DateTime?,
dateTo: freezed == dateTo
? _value.dateTo
: dateTo // ignore: cast_nullable_to_non_nullable
as DateTime?,
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as List<ProductAnalyticItemDto>?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$ProductAnalyticDtoImplCopyWith<$Res>
implements $ProductAnalyticDtoCopyWith<$Res> {
factory _$$ProductAnalyticDtoImplCopyWith(
_$ProductAnalyticDtoImpl value,
$Res Function(_$ProductAnalyticDtoImpl) then,
) = __$$ProductAnalyticDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
@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: "data") List<ProductAnalyticItemDto>? data,
});
}
/// @nodoc
class __$$ProductAnalyticDtoImplCopyWithImpl<$Res>
extends _$ProductAnalyticDtoCopyWithImpl<$Res, _$ProductAnalyticDtoImpl>
implements _$$ProductAnalyticDtoImplCopyWith<$Res> {
__$$ProductAnalyticDtoImplCopyWithImpl(
_$ProductAnalyticDtoImpl _value,
$Res Function(_$ProductAnalyticDtoImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? organizationId = freezed,
Object? outletId = freezed,
Object? dateFrom = freezed,
Object? dateTo = freezed,
Object? data = freezed,
}) {
return _then(
_$ProductAnalyticDtoImpl(
organizationId: freezed == organizationId
? _value.organizationId
: organizationId // ignore: cast_nullable_to_non_nullable
as String?,
outletId: freezed == outletId
? _value.outletId
: outletId // ignore: cast_nullable_to_non_nullable
as String?,
dateFrom: freezed == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
as DateTime?,
dateTo: freezed == dateTo
? _value.dateTo
: dateTo // ignore: cast_nullable_to_non_nullable
as DateTime?,
data: freezed == data
? _value._data
: data // ignore: cast_nullable_to_non_nullable
as List<ProductAnalyticItemDto>?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$ProductAnalyticDtoImpl extends _ProductAnalyticDto {
const _$ProductAnalyticDtoImpl({
@JsonKey(name: "organization_id") this.organizationId,
@JsonKey(name: "outlet_id") this.outletId,
@JsonKey(name: "date_from") this.dateFrom,
@JsonKey(name: "date_to") this.dateTo,
@JsonKey(name: "data") final List<ProductAnalyticItemDto>? data,
}) : _data = data,
super._();
factory _$ProductAnalyticDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$ProductAnalyticDtoImplFromJson(json);
@override
@JsonKey(name: "organization_id")
final String? organizationId;
@override
@JsonKey(name: "outlet_id")
final String? outletId;
@override
@JsonKey(name: "date_from")
final DateTime? dateFrom;
@override
@JsonKey(name: "date_to")
final DateTime? dateTo;
final List<ProductAnalyticItemDto>? _data;
@override
@JsonKey(name: "data")
List<ProductAnalyticItemDto>? get data {
final value = _data;
if (value == null) return null;
if (_data is EqualUnmodifiableListView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'ProductAnalyticDto(organizationId: $organizationId, outletId: $outletId, dateFrom: $dateFrom, dateTo: $dateTo, data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticDtoImpl &&
(identical(other.organizationId, organizationId) ||
other.organizationId == organizationId) &&
(identical(other.outletId, outletId) ||
other.outletId == outletId) &&
(identical(other.dateFrom, dateFrom) ||
other.dateFrom == dateFrom) &&
(identical(other.dateTo, dateTo) || other.dateTo == dateTo) &&
const DeepCollectionEquality().equals(other._data, _data));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
organizationId,
outletId,
dateFrom,
dateTo,
const DeepCollectionEquality().hash(_data),
);
/// Create a copy of ProductAnalyticDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticDtoImplCopyWith<_$ProductAnalyticDtoImpl> get copyWith =>
__$$ProductAnalyticDtoImplCopyWithImpl<_$ProductAnalyticDtoImpl>(
this,
_$identity,
);
@override
Map<String, dynamic> toJson() {
return _$$ProductAnalyticDtoImplToJson(this);
}
}
abstract class _ProductAnalyticDto extends ProductAnalyticDto {
const factory _ProductAnalyticDto({
@JsonKey(name: "organization_id") final String? organizationId,
@JsonKey(name: "outlet_id") final String? outletId,
@JsonKey(name: "date_from") final DateTime? dateFrom,
@JsonKey(name: "date_to") final DateTime? dateTo,
@JsonKey(name: "data") final List<ProductAnalyticItemDto>? data,
}) = _$ProductAnalyticDtoImpl;
const _ProductAnalyticDto._() : super._();
factory _ProductAnalyticDto.fromJson(Map<String, dynamic> json) =
_$ProductAnalyticDtoImpl.fromJson;
@override
@JsonKey(name: "organization_id")
String? get organizationId;
@override
@JsonKey(name: "outlet_id")
String? get outletId;
@override
@JsonKey(name: "date_from")
DateTime? get dateFrom;
@override
@JsonKey(name: "date_to")
DateTime? get dateTo;
@override
@JsonKey(name: "data")
List<ProductAnalyticItemDto>? get data;
/// Create a copy of ProductAnalyticDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticDtoImplCopyWith<_$ProductAnalyticDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ProductAnalyticItemDto _$ProductAnalyticItemDtoFromJson(
Map<String, dynamic> json,
) {
return _ProductAnalyticItemDto.fromJson(json);
}
/// @nodoc
mixin _$ProductAnalyticItemDto {
@JsonKey(name: "product_id")
String? get productId => throw _privateConstructorUsedError;
@JsonKey(name: "product_name")
String? get productName => throw _privateConstructorUsedError;
@JsonKey(name: "category_id")
String? get categoryId => throw _privateConstructorUsedError;
@JsonKey(name: "category_name")
String? get categoryName => throw _privateConstructorUsedError;
@JsonKey(name: "quantity_sold")
int? get quantitySold => throw _privateConstructorUsedError;
@JsonKey(name: "revenue")
int? get revenue => throw _privateConstructorUsedError;
@JsonKey(name: "average_price")
double? get averagePrice => throw _privateConstructorUsedError;
@JsonKey(name: "order_count")
int? get orderCount => throw _privateConstructorUsedError;
/// Serializes this ProductAnalyticItemDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of ProductAnalyticItemDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProductAnalyticItemDtoCopyWith<ProductAnalyticItemDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductAnalyticItemDtoCopyWith<$Res> {
factory $ProductAnalyticItemDtoCopyWith(
ProductAnalyticItemDto value,
$Res Function(ProductAnalyticItemDto) then,
) = _$ProductAnalyticItemDtoCopyWithImpl<$Res, ProductAnalyticItemDto>;
@useResult
$Res call({
@JsonKey(name: "product_id") String? productId,
@JsonKey(name: "product_name") String? productName,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@JsonKey(name: "quantity_sold") int? quantitySold,
@JsonKey(name: "revenue") int? revenue,
@JsonKey(name: "average_price") double? averagePrice,
@JsonKey(name: "order_count") int? orderCount,
});
}
/// @nodoc
class _$ProductAnalyticItemDtoCopyWithImpl<
$Res,
$Val extends ProductAnalyticItemDto
>
implements $ProductAnalyticItemDtoCopyWith<$Res> {
_$ProductAnalyticItemDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductAnalyticItemDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productId = freezed,
Object? productName = freezed,
Object? categoryId = freezed,
Object? categoryName = freezed,
Object? quantitySold = freezed,
Object? revenue = freezed,
Object? averagePrice = freezed,
Object? orderCount = freezed,
}) {
return _then(
_value.copyWith(
productId: freezed == productId
? _value.productId
: productId // ignore: cast_nullable_to_non_nullable
as String?,
productName: freezed == productName
? _value.productName
: productName // ignore: cast_nullable_to_non_nullable
as String?,
categoryId: freezed == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String?,
categoryName: freezed == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String?,
quantitySold: freezed == quantitySold
? _value.quantitySold
: quantitySold // ignore: cast_nullable_to_non_nullable
as int?,
revenue: freezed == revenue
? _value.revenue
: revenue // ignore: cast_nullable_to_non_nullable
as int?,
averagePrice: freezed == averagePrice
? _value.averagePrice
: averagePrice // ignore: cast_nullable_to_non_nullable
as double?,
orderCount: freezed == orderCount
? _value.orderCount
: orderCount // ignore: cast_nullable_to_non_nullable
as int?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$ProductAnalyticItemDtoImplCopyWith<$Res>
implements $ProductAnalyticItemDtoCopyWith<$Res> {
factory _$$ProductAnalyticItemDtoImplCopyWith(
_$ProductAnalyticItemDtoImpl value,
$Res Function(_$ProductAnalyticItemDtoImpl) then,
) = __$$ProductAnalyticItemDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
@JsonKey(name: "product_id") String? productId,
@JsonKey(name: "product_name") String? productName,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@JsonKey(name: "quantity_sold") int? quantitySold,
@JsonKey(name: "revenue") int? revenue,
@JsonKey(name: "average_price") double? averagePrice,
@JsonKey(name: "order_count") int? orderCount,
});
}
/// @nodoc
class __$$ProductAnalyticItemDtoImplCopyWithImpl<$Res>
extends
_$ProductAnalyticItemDtoCopyWithImpl<$Res, _$ProductAnalyticItemDtoImpl>
implements _$$ProductAnalyticItemDtoImplCopyWith<$Res> {
__$$ProductAnalyticItemDtoImplCopyWithImpl(
_$ProductAnalyticItemDtoImpl _value,
$Res Function(_$ProductAnalyticItemDtoImpl) _then,
) : super(_value, _then);
/// Create a copy of ProductAnalyticItemDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? productId = freezed,
Object? productName = freezed,
Object? categoryId = freezed,
Object? categoryName = freezed,
Object? quantitySold = freezed,
Object? revenue = freezed,
Object? averagePrice = freezed,
Object? orderCount = freezed,
}) {
return _then(
_$ProductAnalyticItemDtoImpl(
productId: freezed == productId
? _value.productId
: productId // ignore: cast_nullable_to_non_nullable
as String?,
productName: freezed == productName
? _value.productName
: productName // ignore: cast_nullable_to_non_nullable
as String?,
categoryId: freezed == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String?,
categoryName: freezed == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String?,
quantitySold: freezed == quantitySold
? _value.quantitySold
: quantitySold // ignore: cast_nullable_to_non_nullable
as int?,
revenue: freezed == revenue
? _value.revenue
: revenue // ignore: cast_nullable_to_non_nullable
as int?,
averagePrice: freezed == averagePrice
? _value.averagePrice
: averagePrice // ignore: cast_nullable_to_non_nullable
as double?,
orderCount: freezed == orderCount
? _value.orderCount
: orderCount // ignore: cast_nullable_to_non_nullable
as int?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$ProductAnalyticItemDtoImpl extends _ProductAnalyticItemDto {
const _$ProductAnalyticItemDtoImpl({
@JsonKey(name: "product_id") this.productId,
@JsonKey(name: "product_name") this.productName,
@JsonKey(name: "category_id") this.categoryId,
@JsonKey(name: "category_name") this.categoryName,
@JsonKey(name: "quantity_sold") this.quantitySold,
@JsonKey(name: "revenue") this.revenue,
@JsonKey(name: "average_price") this.averagePrice,
@JsonKey(name: "order_count") this.orderCount,
}) : super._();
factory _$ProductAnalyticItemDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$ProductAnalyticItemDtoImplFromJson(json);
@override
@JsonKey(name: "product_id")
final String? productId;
@override
@JsonKey(name: "product_name")
final String? productName;
@override
@JsonKey(name: "category_id")
final String? categoryId;
@override
@JsonKey(name: "category_name")
final String? categoryName;
@override
@JsonKey(name: "quantity_sold")
final int? quantitySold;
@override
@JsonKey(name: "revenue")
final int? revenue;
@override
@JsonKey(name: "average_price")
final double? averagePrice;
@override
@JsonKey(name: "order_count")
final int? orderCount;
@override
String toString() {
return 'ProductAnalyticItemDto(productId: $productId, productName: $productName, categoryId: $categoryId, categoryName: $categoryName, quantitySold: $quantitySold, revenue: $revenue, averagePrice: $averagePrice, orderCount: $orderCount)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProductAnalyticItemDtoImpl &&
(identical(other.productId, productId) ||
other.productId == productId) &&
(identical(other.productName, productName) ||
other.productName == productName) &&
(identical(other.categoryId, categoryId) ||
other.categoryId == categoryId) &&
(identical(other.categoryName, categoryName) ||
other.categoryName == categoryName) &&
(identical(other.quantitySold, quantitySold) ||
other.quantitySold == quantitySold) &&
(identical(other.revenue, revenue) || other.revenue == revenue) &&
(identical(other.averagePrice, averagePrice) ||
other.averagePrice == averagePrice) &&
(identical(other.orderCount, orderCount) ||
other.orderCount == orderCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
productId,
productName,
categoryId,
categoryName,
quantitySold,
revenue,
averagePrice,
orderCount,
);
/// Create a copy of ProductAnalyticItemDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProductAnalyticItemDtoImplCopyWith<_$ProductAnalyticItemDtoImpl>
get copyWith =>
__$$ProductAnalyticItemDtoImplCopyWithImpl<_$ProductAnalyticItemDtoImpl>(
this,
_$identity,
);
@override
Map<String, dynamic> toJson() {
return _$$ProductAnalyticItemDtoImplToJson(this);
}
}
abstract class _ProductAnalyticItemDto extends ProductAnalyticItemDto {
const factory _ProductAnalyticItemDto({
@JsonKey(name: "product_id") final String? productId,
@JsonKey(name: "product_name") final String? productName,
@JsonKey(name: "category_id") final String? categoryId,
@JsonKey(name: "category_name") final String? categoryName,
@JsonKey(name: "quantity_sold") final int? quantitySold,
@JsonKey(name: "revenue") final int? revenue,
@JsonKey(name: "average_price") final double? averagePrice,
@JsonKey(name: "order_count") final int? orderCount,
}) = _$ProductAnalyticItemDtoImpl;
const _ProductAnalyticItemDto._() : super._();
factory _ProductAnalyticItemDto.fromJson(Map<String, dynamic> json) =
_$ProductAnalyticItemDtoImpl.fromJson;
@override
@JsonKey(name: "product_id")
String? get productId;
@override
@JsonKey(name: "product_name")
String? get productName;
@override
@JsonKey(name: "category_id")
String? get categoryId;
@override
@JsonKey(name: "category_name")
String? get categoryName;
@override
@JsonKey(name: "quantity_sold")
int? get quantitySold;
@override
@JsonKey(name: "revenue")
int? get revenue;
@override
@JsonKey(name: "average_price")
double? get averagePrice;
@override
@JsonKey(name: "order_count")
int? get orderCount;
/// Create a copy of ProductAnalyticItemDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProductAnalyticItemDtoImplCopyWith<_$ProductAnalyticItemDtoImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -217,3 +217,55 @@ Map<String, dynamic> _$$SalesAnalyticItemDtoImplToJson(
'discount': instance.discount, 'discount': instance.discount,
'net_sales': instance.netSales, 'net_sales': instance.netSales,
}; };
_$ProductAnalyticDtoImpl _$$ProductAnalyticDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProductAnalyticDtoImpl(
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),
data: (json['data'] as List<dynamic>?)
?.map((e) => ProductAnalyticItemDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ProductAnalyticDtoImplToJson(
_$ProductAnalyticDtoImpl instance,
) => <String, dynamic>{
'organization_id': instance.organizationId,
'outlet_id': instance.outletId,
'date_from': instance.dateFrom?.toIso8601String(),
'date_to': instance.dateTo?.toIso8601String(),
'data': instance.data,
};
_$ProductAnalyticItemDtoImpl _$$ProductAnalyticItemDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProductAnalyticItemDtoImpl(
productId: json['product_id'] as String?,
productName: json['product_name'] as String?,
categoryId: json['category_id'] as String?,
categoryName: json['category_name'] as String?,
quantitySold: (json['quantity_sold'] as num?)?.toInt(),
revenue: (json['revenue'] as num?)?.toInt(),
averagePrice: (json['average_price'] as num?)?.toDouble(),
orderCount: (json['order_count'] as num?)?.toInt(),
);
Map<String, dynamic> _$$ProductAnalyticItemDtoImplToJson(
_$ProductAnalyticItemDtoImpl instance,
) => <String, dynamic>{
'product_id': instance.productId,
'product_name': instance.productName,
'category_id': instance.categoryId,
'category_name': instance.categoryName,
'quantity_sold': instance.quantitySold,
'revenue': instance.revenue,
'average_price': instance.averagePrice,
'order_count': instance.orderCount,
};

View File

@ -76,4 +76,33 @@ class AnalyticRemoteDataProvider {
return DC.error(AnalyticFailure.serverError(e)); return DC.error(AnalyticFailure.serverError(e));
} }
} }
Future<DC<AnalyticFailure, ProductAnalyticDto>> fetchProducts({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final response = await _apiClient.get(
ApiPath.analyticProducts,
params: {
'date_from': dateFrom.toServerDate(),
'date_to': dateTo.toServerDate(),
},
headers: getAuthorizationHeader(),
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
}
final products = ProductAnalyticDto.fromJson(
response.data['data'] as Map<String, dynamic>,
);
return DC.data(products);
} on ApiFailure catch (e, s) {
log('fetchProducts', name: _logName, error: e, stackTrace: s);
return DC.error(AnalyticFailure.serverError(e));
}
}
} }

View File

@ -0,0 +1,57 @@
part of '../analytic_dtos.dart';
@freezed
class ProductAnalyticDto with _$ProductAnalyticDto {
const ProductAnalyticDto._();
const factory ProductAnalyticDto({
@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: "data") List<ProductAnalyticItemDto>? data,
}) = _ProductAnalyticDto;
factory ProductAnalyticDto.fromJson(Map<String, dynamic> json) =>
_$ProductAnalyticDtoFromJson(json);
// Optional mapping ke domain entity
ProductAnalytic toDomain() => ProductAnalytic(
organizationId: organizationId ?? '',
outletId: outletId ?? '',
dateFrom: dateFrom ?? DateTime.now(),
dateTo: dateTo ?? DateTime.now(),
data: data?.map((e) => e.toDomain()).toList() ?? [],
);
}
@freezed
class ProductAnalyticItemDto with _$ProductAnalyticItemDto {
const ProductAnalyticItemDto._();
const factory ProductAnalyticItemDto({
@JsonKey(name: "product_id") String? productId,
@JsonKey(name: "product_name") String? productName,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@JsonKey(name: "quantity_sold") int? quantitySold,
@JsonKey(name: "revenue") int? revenue,
@JsonKey(name: "average_price") double? averagePrice,
@JsonKey(name: "order_count") int? orderCount,
}) = _ProductAnalyticItemDto;
factory ProductAnalyticItemDto.fromJson(Map<String, dynamic> json) =>
_$ProductAnalyticItemDtoFromJson(json);
// Optional mapping ke domain entity
ProductAnalyticItem toDomain() => ProductAnalyticItem(
productId: productId ?? '',
productName: productName ?? '',
categoryId: categoryId ?? '',
categoryName: categoryName ?? '',
quantitySold: quantitySold ?? 0,
revenue: revenue ?? 0,
averagePrice: averagePrice ?? 0.0,
orderCount: orderCount ?? 0,
);
}

View File

@ -61,4 +61,28 @@ class AnalyticRepository implements IAnalyticRepository {
return left(const AnalyticFailure.unexpectedError()); return left(const AnalyticFailure.unexpectedError());
} }
} }
@override
Future<Either<AnalyticFailure, ProductAnalytic>> getProducts({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchProducts(
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
final products = result.data!.toDomain();
return right(products);
} catch (e) {
log('getProductsError', name: _logName, error: e);
return left(const AnalyticFailure.unexpectedError());
}
}
} }

View File

@ -11,6 +11,8 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:apskel_pos_flutter_v2/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart' import 'package:apskel_pos_flutter_v2/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart'
as _i80; as _i80;
import 'package:apskel_pos_flutter_v2/application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart'
as _i268;
import 'package:apskel_pos_flutter_v2/application/analytic/sales_analytic_loader/sales_analytic_loader_bloc.dart' import 'package:apskel_pos_flutter_v2/application/analytic/sales_analytic_loader/sales_analytic_loader_bloc.dart'
as _i413; as _i413;
import 'package:apskel_pos_flutter_v2/application/auth/auth_bloc.dart' as _i343; import 'package:apskel_pos_flutter_v2/application/auth/auth_bloc.dart' as _i343;
@ -298,6 +300,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i413.SalesAnalyticLoaderBloc>( gh.factory<_i413.SalesAnalyticLoaderBloc>(
() => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), () => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
); );
gh.factory<_i268.ProductAnalyticLoaderBloc>(
() => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
return this; return this;
} }
} }

View File

@ -8,18 +8,20 @@ class ReportSummaryCard extends StatelessWidget {
final IconData icon; final IconData icon;
final String title; final String title;
final String value; final String value;
final String? subtitle;
const ReportSummaryCard({ const ReportSummaryCard({
super.key, super.key,
required this.color, required this.color,
required this.icon, required this.icon,
required this.title, required this.title,
required this.value, required this.value,
this.subtitle,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(18), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.white, color: AppColor.white,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
@ -55,6 +57,16 @@ class ReportSummaryCard extends StatelessWidget {
color: AppColor.textPrimary, color: AppColor.textPrimary,
), ),
), ),
if (subtitle != null) ...[
SpaceHeight(2),
Text(
subtitle!,
style: AppStyle.sm.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.textSecondary,
),
),
],
], ],
), ),
), ),

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart'; import '../../../../../application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart';
import '../../../../../application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart';
import '../../../../../application/analytic/sales_analytic_loader/sales_analytic_loader_bloc.dart'; import '../../../../../application/analytic/sales_analytic_loader/sales_analytic_loader_bloc.dart';
import '../../../../../application/report/report_bloc.dart'; import '../../../../../application/report/report_bloc.dart';
import '../../../../../common/data/report_menu.dart'; import '../../../../../common/data/report_menu.dart';
@ -14,6 +15,7 @@ import '../../../../components/picker/date_range_picker.dart';
import '../../../../components/spaces/space.dart'; import '../../../../components/spaces/space.dart';
import '../../../../router/app_router.gr.dart'; import '../../../../router/app_router.gr.dart';
import 'sections/report_dashboard_section.dart'; import 'sections/report_dashboard_section.dart';
import 'sections/report_product_section.dart';
import 'sections/report_sales_section.dart'; import 'sections/report_sales_section.dart';
import 'widgets/report_menu_card.dart'; import 'widgets/report_menu_card.dart';
@ -135,7 +137,20 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
); );
}, },
), ),
3 => Text(state.title), 3 =>
BlocBuilder<
ProductAnalyticLoaderBloc,
ProductAnalyticLoaderState
>(
builder: (context, product) {
return ReportProductSection(
state: product,
menu: reportMenus[state.selectedMenu],
startDate: state.startDate,
endDate: state.endDate,
);
},
),
4 => Text(state.title), 4 => Text(state.title),
5 => Text(state.title), 5 => Text(state.title),
6 => Text(state.title), 6 => Text(state.title),
@ -173,6 +188,12 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
), ),
); );
case 3: case 3:
return context.read<ProductAnalyticLoaderBloc>().add(
ProductAnalyticLoaderEvent.fetched(
startDate: state.startDate,
endDate: state.endDate,
),
);
case 4: case 4:
case 5: case 5:
case 6: case 6:
@ -204,6 +225,15 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
), ),
), ),
), ),
BlocProvider(
create: (context) => getIt<ProductAnalyticLoaderBloc>()
..add(
ProductAnalyticLoaderEvent.fetched(
startDate: DateTime.now().subtract(const Duration(days: 30)),
endDate: DateTime.now(),
),
),
),
], ],
child: this, child: this,
); );

View File

@ -0,0 +1,314 @@
import 'package:flutter/material.dart';
import '../../../../../../application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart';
import '../../../../../../common/data/report_menu.dart';
import '../../../../../../common/extension/extension.dart';
import '../../../../../../common/theme/theme.dart';
import '../../../../../../domain/analytic/analytic.dart';
import '../../../../../components/loader/loader_with_text.dart';
import '../../../../../components/spaces/space.dart';
import '../../../../../components/widgets/report/report_header.dart';
import '../../../../../components/widgets/report/report_summary_card.dart';
class ReportProductSection extends StatelessWidget {
final ReportMenu menu;
final ProductAnalyticLoaderState state;
final DateTime startDate;
final DateTime endDate;
const ReportProductSection({
super.key,
required this.state,
required this.menu,
required this.startDate,
required this.endDate,
});
@override
Widget build(BuildContext context) {
if (state.isFetching) {
return const Center(child: LoaderWithText());
}
return Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
ReportHeader(
menu: menu,
endDate: endDate,
startDate: startDate,
),
Container(
height: 90,
margin: EdgeInsets.only(top: 16),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.productAnalytic.categories.length,
itemBuilder: (context, index) {
final category = state.productAnalytic.categories[index];
return Container(
width: 200,
padding: EdgeInsets.only(
right:
index ==
state.productAnalytic.categories.length - 1
? 0
: 12,
),
child: ReportSummaryCard(
color: AppColor.primary,
icon: Icons.category_outlined,
title: category.categoryName,
value: category.totalRevenue.currencyFormatRpV2,
subtitle: "${category.productCount} Item",
),
);
},
),
),
Container(
margin: EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Produk Berkinerja Terbaik',
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.textPrimary,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: AppColor.primary,
width: 1,
),
),
child: Text(
'Berdasarkan Pendapatan',
style: AppStyle.xs.copyWith(
fontWeight: FontWeight.w500,
color: AppColor.primary,
fontSize: 10,
),
),
),
],
),
SpaceHeight(12),
ListView.builder(
itemCount: state.productAnalytic.data.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final product = state.productAnalytic.data[index];
return _buildProductItem(
rank: index + 1,
product: product,
isTopPerformer:
product == state.productAnalytic.bestProduct,
categoryColor: AppColor.primary,
);
},
),
],
),
),
],
),
),
),
_buildBottomSummary(),
],
);
}
Widget _buildBottomSummary() {
return Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: BoxDecoration(color: AppColor.white),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColor.success.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.success, width: 1),
),
child: Row(
children: [
Icon(Icons.star, color: AppColor.success, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
'${state.productAnalytic.bestProduct.productName} memimpin dengan ${state.productAnalytic.bestProduct.quantitySold} unit terjual dan pendapatan ${state.productAnalytic.bestProduct.revenue.currencyFormatRpV2}',
style: AppStyle.sm.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.success,
),
),
),
],
),
),
],
),
);
}
Widget _buildProductItem({
required int rank,
required ProductAnalyticItem product,
required bool isTopPerformer,
required Color categoryColor,
}) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: isTopPerformer
? AppColor.primary.withOpacity(0.1)
: AppColor.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isTopPerformer ? AppColor.primary : AppColor.border,
width: isTopPerformer ? 2 : 1,
),
),
child: Row(
children: [
// Rank Badge
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: isTopPerformer ? AppColor.primary : AppColor.textSecondary,
borderRadius: BorderRadius.circular(14),
),
child: Center(
child: Text(
'$rank',
style: AppStyle.sm.copyWith(
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
),
SpaceWidth(12),
// Product Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
product.productName,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.textPrimary,
),
),
),
Row(
children: [
if (isTopPerformer)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: AppColor.success,
borderRadius: BorderRadius.circular(3),
),
child: Text(
'BEST',
style: AppStyle.xs.copyWith(
fontSize: 8,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
Text(
product.revenue.currencyFormatRpV2,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.primary,
),
),
],
),
],
),
SpaceHeight(6),
Row(
children: [
// Category Badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: categoryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
product.categoryName,
style: AppStyle.xs.copyWith(
fontSize: 9,
fontWeight: FontWeight.w500,
color: categoryColor,
),
),
),
const SizedBox(width: 8),
// Stats
Expanded(
child: Text(
'${product.quantitySold} units • ${product.orderCount} orders • Avg ${product.averagePrice.round()}',
style: AppStyle.xs.copyWith(
fontSize: 11,
fontWeight: FontWeight.w400,
color: AppColor.textSecondary,
),
),
),
],
),
],
),
),
],
),
);
}
}