From 06d777290d4c4ef40f99239c91d87238fd9ddb8a Mon Sep 17 00:00:00 2001 From: efrilm Date: Mon, 18 Aug 2025 17:35:03 +0700 Subject: [PATCH] feat: dashboard analytic range date --- .../dashboard_analytic_loader_bloc.dart | 7 +- ...ashboard_analytic_loader_bloc.freezed.dart | 228 +++++++++++++++++- .../dashboard_analytic_loader_event.dart | 4 + .../dashboard_analytic_loader_state.dart | 4 + .../pages/report/report_page.dart | 173 ++++++++----- 5 files changed, 345 insertions(+), 71 deletions(-) diff --git a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart index d3e7383..264695b 100644 --- a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart +++ b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart @@ -24,6 +24,9 @@ class DashboardAnalyticLoaderBloc Emitter emit, ) { return event.map( + rangeDateChanged: (e) async { + emit(state.copyWith(dateFrom: e.dateFrom, dateTo: e.dateTo)); + }, fetched: (e) async { emit( state.copyWith( @@ -33,8 +36,8 @@ class DashboardAnalyticLoaderBloc ); final result = await _repository.getDashboard( - dateFrom: DateTime.now().subtract(const Duration(days: 30)), - dateTo: DateTime.now(), + dateFrom: state.dateFrom, + dateTo: state.dateTo, ); var data = result.fold( diff --git a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.freezed.dart b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.freezed.dart index 4976136..a11d71d 100644 --- a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.freezed.dart +++ b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.freezed.dart @@ -19,27 +19,34 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$DashboardAnalyticLoaderEvent { @optionalTypeArgs TResult when({ + required TResult Function(DateTime dateFrom, DateTime dateTo) + rangeDateChanged, required TResult Function() fetched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ + TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, TResult? Function()? fetched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ + TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, TResult Function()? fetched, required TResult orElse(), }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult map({ + required TResult Function(_RangeDateChanged value) rangeDateChanged, required TResult Function(_Fetched value) fetched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ + TResult? Function(_RangeDateChanged value)? rangeDateChanged, TResult? Function(_Fetched value)? fetched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ + TResult Function(_RangeDateChanged value)? rangeDateChanged, TResult Function(_Fetched value)? fetched, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -74,6 +81,165 @@ class _$DashboardAnalyticLoaderEventCopyWithImpl< /// with the given fields replaced by the non-null parameter values. } +/// @nodoc +abstract class _$$RangeDateChangedImplCopyWith<$Res> { + factory _$$RangeDateChangedImplCopyWith( + _$RangeDateChangedImpl value, + $Res Function(_$RangeDateChangedImpl) then, + ) = __$$RangeDateChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({DateTime dateFrom, DateTime dateTo}); +} + +/// @nodoc +class __$$RangeDateChangedImplCopyWithImpl<$Res> + extends + _$DashboardAnalyticLoaderEventCopyWithImpl<$Res, _$RangeDateChangedImpl> + implements _$$RangeDateChangedImplCopyWith<$Res> { + __$$RangeDateChangedImplCopyWithImpl( + _$RangeDateChangedImpl _value, + $Res Function(_$RangeDateChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of DashboardAnalyticLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? dateFrom = null, Object? dateTo = null}) { + return _then( + _$RangeDateChangedImpl( + null == dateFrom + ? _value.dateFrom + : dateFrom // ignore: cast_nullable_to_non_nullable + as DateTime, + null == dateTo + ? _value.dateTo + : dateTo // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc + +class _$RangeDateChangedImpl implements _RangeDateChanged { + const _$RangeDateChangedImpl(this.dateFrom, this.dateTo); + + @override + final DateTime dateFrom; + @override + final DateTime dateTo; + + @override + String toString() { + return 'DashboardAnalyticLoaderEvent.rangeDateChanged(dateFrom: $dateFrom, dateTo: $dateTo)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RangeDateChangedImpl && + (identical(other.dateFrom, dateFrom) || + other.dateFrom == dateFrom) && + (identical(other.dateTo, dateTo) || other.dateTo == dateTo)); + } + + @override + int get hashCode => Object.hash(runtimeType, dateFrom, dateTo); + + /// Create a copy of DashboardAnalyticLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith => + __$$RangeDateChangedImplCopyWithImpl<_$RangeDateChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime dateFrom, DateTime dateTo) + rangeDateChanged, + required TResult Function() fetched, + }) { + return rangeDateChanged(dateFrom, dateTo); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, + TResult? Function()? fetched, + }) { + return rangeDateChanged?.call(dateFrom, dateTo); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, + TResult Function()? fetched, + required TResult orElse(), + }) { + if (rangeDateChanged != null) { + return rangeDateChanged(dateFrom, dateTo); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RangeDateChanged value) rangeDateChanged, + required TResult Function(_Fetched value) fetched, + }) { + return rangeDateChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RangeDateChanged value)? rangeDateChanged, + TResult? Function(_Fetched value)? fetched, + }) { + return rangeDateChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RangeDateChanged value)? rangeDateChanged, + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (rangeDateChanged != null) { + return rangeDateChanged(this); + } + return orElse(); + } +} + +abstract class _RangeDateChanged implements DashboardAnalyticLoaderEvent { + const factory _RangeDateChanged( + final DateTime dateFrom, + final DateTime dateTo, + ) = _$RangeDateChangedImpl; + + DateTime get dateFrom; + DateTime get dateTo; + + /// Create a copy of DashboardAnalyticLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$FetchedImplCopyWith<$Res> { factory _$$FetchedImplCopyWith( @@ -116,19 +282,27 @@ class _$FetchedImpl implements _Fetched { @override @optionalTypeArgs - TResult when({required TResult Function() fetched}) { + TResult when({ + required TResult Function(DateTime dateFrom, DateTime dateTo) + rangeDateChanged, + required TResult Function() fetched, + }) { return fetched(); } @override @optionalTypeArgs - TResult? whenOrNull({TResult? Function()? fetched}) { + TResult? whenOrNull({ + TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, + TResult? Function()? fetched, + }) { return fetched?.call(); } @override @optionalTypeArgs TResult maybeWhen({ + TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged, TResult Function()? fetched, required TResult orElse(), }) { @@ -141,6 +315,7 @@ class _$FetchedImpl implements _Fetched { @override @optionalTypeArgs TResult map({ + required TResult Function(_RangeDateChanged value) rangeDateChanged, required TResult Function(_Fetched value) fetched, }) { return fetched(this); @@ -149,6 +324,7 @@ class _$FetchedImpl implements _Fetched { @override @optionalTypeArgs TResult? mapOrNull({ + TResult? Function(_RangeDateChanged value)? rangeDateChanged, TResult? Function(_Fetched value)? fetched, }) { return fetched?.call(this); @@ -157,6 +333,7 @@ class _$FetchedImpl implements _Fetched { @override @optionalTypeArgs TResult maybeMap({ + TResult Function(_RangeDateChanged value)? rangeDateChanged, TResult Function(_Fetched value)? fetched, required TResult orElse(), }) { @@ -177,6 +354,8 @@ mixin _$DashboardAnalyticLoaderState { Option get failureOptionDashboardAnalytic => throw _privateConstructorUsedError; bool get isFetching => throw _privateConstructorUsedError; + DateTime get dateFrom => throw _privateConstructorUsedError; + DateTime get dateTo => throw _privateConstructorUsedError; /// Create a copy of DashboardAnalyticLoaderState /// with the given fields replaced by the non-null parameter values. @@ -200,6 +379,8 @@ abstract class $DashboardAnalyticLoaderStateCopyWith<$Res> { DashboardAnalytic dashboardAnalytic, Option failureOptionDashboardAnalytic, bool isFetching, + DateTime dateFrom, + DateTime dateTo, }); $DashboardAnalyticCopyWith<$Res> get dashboardAnalytic; @@ -226,6 +407,8 @@ class _$DashboardAnalyticLoaderStateCopyWithImpl< Object? dashboardAnalytic = null, Object? failureOptionDashboardAnalytic = null, Object? isFetching = null, + Object? dateFrom = null, + Object? dateTo = null, }) { return _then( _value.copyWith( @@ -242,6 +425,14 @@ class _$DashboardAnalyticLoaderStateCopyWithImpl< ? _value.isFetching : isFetching // ignore: cast_nullable_to_non_nullable as bool, + 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, ) as $Val, ); @@ -271,6 +462,8 @@ abstract class _$$DashboardAnalyticLoaderStateImplCopyWith<$Res> DashboardAnalytic dashboardAnalytic, Option failureOptionDashboardAnalytic, bool isFetching, + DateTime dateFrom, + DateTime dateTo, }); @override @@ -298,6 +491,8 @@ class __$$DashboardAnalyticLoaderStateImplCopyWithImpl<$Res> Object? dashboardAnalytic = null, Object? failureOptionDashboardAnalytic = null, Object? isFetching = null, + Object? dateFrom = null, + Object? dateTo = null, }) { return _then( _$DashboardAnalyticLoaderStateImpl( @@ -313,6 +508,14 @@ class __$$DashboardAnalyticLoaderStateImplCopyWithImpl<$Res> ? _value.isFetching : isFetching // ignore: cast_nullable_to_non_nullable as bool, + 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, ), ); } @@ -326,6 +529,8 @@ class _$DashboardAnalyticLoaderStateImpl required this.dashboardAnalytic, required this.failureOptionDashboardAnalytic, this.isFetching = false, + required this.dateFrom, + required this.dateTo, }); @override @@ -335,10 +540,14 @@ class _$DashboardAnalyticLoaderStateImpl @override @JsonKey() final bool isFetching; + @override + final DateTime dateFrom; + @override + final DateTime dateTo; @override String toString() { - return 'DashboardAnalyticLoaderState(dashboardAnalytic: $dashboardAnalytic, failureOptionDashboardAnalytic: $failureOptionDashboardAnalytic, isFetching: $isFetching)'; + return 'DashboardAnalyticLoaderState(dashboardAnalytic: $dashboardAnalytic, failureOptionDashboardAnalytic: $failureOptionDashboardAnalytic, isFetching: $isFetching, dateFrom: $dateFrom, dateTo: $dateTo)'; } @override @@ -355,7 +564,10 @@ class _$DashboardAnalyticLoaderStateImpl other.failureOptionDashboardAnalytic == failureOptionDashboardAnalytic) && (identical(other.isFetching, isFetching) || - other.isFetching == isFetching)); + other.isFetching == isFetching) && + (identical(other.dateFrom, dateFrom) || + other.dateFrom == dateFrom) && + (identical(other.dateTo, dateTo) || other.dateTo == dateTo)); } @override @@ -364,6 +576,8 @@ class _$DashboardAnalyticLoaderStateImpl dashboardAnalytic, failureOptionDashboardAnalytic, isFetching, + dateFrom, + dateTo, ); /// Create a copy of DashboardAnalyticLoaderState @@ -386,6 +600,8 @@ abstract class _DashboardAnalyticLoaderState required final DashboardAnalytic dashboardAnalytic, required final Option failureOptionDashboardAnalytic, final bool isFetching, + required final DateTime dateFrom, + required final DateTime dateTo, }) = _$DashboardAnalyticLoaderStateImpl; @override @@ -394,6 +610,10 @@ abstract class _DashboardAnalyticLoaderState Option get failureOptionDashboardAnalytic; @override bool get isFetching; + @override + DateTime get dateFrom; + @override + DateTime get dateTo; /// Create a copy of DashboardAnalyticLoaderState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_event.dart b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_event.dart index 4ac3dbd..5787cbe 100644 --- a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_event.dart +++ b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_event.dart @@ -2,5 +2,9 @@ part of 'dashboard_analytic_loader_bloc.dart'; @freezed class DashboardAnalyticLoaderEvent with _$DashboardAnalyticLoaderEvent { + const factory DashboardAnalyticLoaderEvent.rangeDateChanged( + DateTime dateFrom, + DateTime dateTo, + ) = _RangeDateChanged; const factory DashboardAnalyticLoaderEvent.fetched() = _Fetched; } diff --git a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_state.dart b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_state.dart index 07937ae..b0a3917 100644 --- a/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_state.dart +++ b/lib/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_state.dart @@ -6,11 +6,15 @@ class DashboardAnalyticLoaderState with _$DashboardAnalyticLoaderState { required DashboardAnalytic dashboardAnalytic, required Option failureOptionDashboardAnalytic, @Default(false) bool isFetching, + required DateTime dateFrom, + required DateTime dateTo, }) = _DashboardAnalyticLoaderState; factory DashboardAnalyticLoaderState.initial() => DashboardAnalyticLoaderState( dashboardAnalytic: DashboardAnalytic.empty(), failureOptionDashboardAnalytic: none(), + dateFrom: DateTime.now().subtract(const Duration(days: 30)), + dateTo: DateTime.now(), ); } diff --git a/lib/presentation/pages/report/report_page.dart b/lib/presentation/pages/report/report_page.dart index 60be8f5..64b99e8 100644 --- a/lib/presentation/pages/report/report_page.dart +++ b/lib/presentation/pages/report/report_page.dart @@ -9,6 +9,7 @@ import '../../../common/theme/theme.dart'; import '../../../injection.dart'; import '../../components/appbar/appbar.dart'; import '../../components/button/button.dart'; +import '../../components/field/date_range_picker_field.dart'; import '../../components/spacer/spacer.dart'; import 'widgets/payment_method.dart'; import 'widgets/quick_stats.dart'; @@ -87,78 +88,120 @@ class _ReportPageState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColor.background, - body: - BlocBuilder< - DashboardAnalyticLoaderBloc, - DashboardAnalyticLoaderState - >( - builder: (context, state) { - return CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 120, - floating: false, - pinned: true, - backgroundColor: AppColor.primary, - centerTitle: false, - flexibleSpace: CustomAppBar( - title: 'Laporan', - isBack: false, + return BlocListener< + DashboardAnalyticLoaderBloc, + DashboardAnalyticLoaderState + >( + listenWhen: (previous, current) => + previous.dateFrom != current.dateFrom || + previous.dateTo != current.dateTo, + listener: (context, state) { + context.read().add( + DashboardAnalyticLoaderEvent.fetched(), + ); + }, + child: Scaffold( + backgroundColor: AppColor.background, + body: + BlocBuilder< + DashboardAnalyticLoaderBloc, + DashboardAnalyticLoaderState + >( + builder: (context, state) { + return CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 120, + floating: false, + pinned: true, + backgroundColor: AppColor.primary, + centerTitle: false, + flexibleSpace: CustomAppBar( + title: 'Laporan', + isBack: false, + ), + actions: [ + ActionIconButton( + onTap: () {}, + icon: LineIcons.download, + ), + ActionIconButton(onTap: () {}, icon: LineIcons.filter), + SpaceWidth(8), + ], ), - actions: [ - ActionIconButton(onTap: () {}, icon: LineIcons.download), - ActionIconButton(onTap: () {}, icon: LineIcons.filter), - SpaceWidth(8), - ], - ), - // Content - SliverPadding( - padding: EdgeInsets.all(AppValue.padding), - sliver: SliverList( - delegate: SliverChildListDelegate([ - FadeTransition( + SliverToBoxAdapter( + child: SlideTransition( + position: _slideAnimation, + child: FadeTransition( opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Column( - children: [ - ReportRevenueSummary( - overview: state.dashboardAnalytic.overview, - rotationAnimation: _rotationAnimation, - ), - const SpaceHeight(24), - ReportQuickStats( - overview: state.dashboardAnalytic.overview, - ), - const SpaceHeight(24), - ReportSales( - salesData: - state.dashboardAnalytic.recentSales, - ), - const SpaceHeight(24), - ReportPaymentMethod( - paymentMethods: - state.dashboardAnalytic.paymentMethods, - ), - const SpaceHeight(24), - ReportTopProduct( - products: state.dashboardAnalytic.topProducts, - ), - const SpaceHeight(24), - ], + child: Padding( + padding: const EdgeInsets.all(16.0), + child: DateRangePickerField( + maxDate: DateTime.now(), + startDate: state.dateFrom, + endDate: state.dateTo, + onChanged: (startDate, endDate) { + context.read().add( + DashboardAnalyticLoaderEvent.rangeDateChanged( + startDate!, + endDate!, + ), + ); + }, ), ), ), - ]), + ), ), - ), - ], - ); - }, - ), + + // Content + SliverPadding( + padding: EdgeInsets.all(AppValue.padding), + sliver: SliverList( + delegate: SliverChildListDelegate([ + FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + children: [ + ReportRevenueSummary( + overview: state.dashboardAnalytic.overview, + rotationAnimation: _rotationAnimation, + ), + const SpaceHeight(24), + ReportQuickStats( + overview: state.dashboardAnalytic.overview, + ), + const SpaceHeight(24), + ReportSales( + salesData: + state.dashboardAnalytic.recentSales, + ), + const SpaceHeight(24), + ReportPaymentMethod( + paymentMethods: + state.dashboardAnalytic.paymentMethods, + ), + const SpaceHeight(24), + ReportTopProduct( + products: + state.dashboardAnalytic.topProducts, + ), + const SpaceHeight(24), + ], + ), + ), + ), + ]), + ), + ), + ], + ); + }, + ), + ), ); } }