diff --git a/lib/application/report/report_bloc.dart b/lib/application/report/report_bloc.dart new file mode 100644 index 0000000..351b3ba --- /dev/null +++ b/lib/application/report/report_bloc.dart @@ -0,0 +1,34 @@ +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../common/extension/extension.dart'; + +part 'report_event.dart'; +part 'report_state.dart'; +part 'report_bloc.freezed.dart'; + +@injectable +class ReportBloc extends Bloc { + ReportBloc() : super(ReportState.initial()) { + on(_onReportEvent); + } + + Future _onReportEvent(ReportEvent event, Emitter emit) { + return event.map( + dateChanged: (e) async { + emit( + state.copyWith( + startDate: e.startDate, + endDate: e.endDate, + rangeDateFormatted: + '${e.startDate.toFormattedDate()} - ${e.endDate.toFormattedDate()}', + ), + ); + }, + menuChanged: (e) async { + emit(state.copyWith(selectedMenu: e.index, title: e.title)); + }, + ); + } +} diff --git a/lib/application/report/report_bloc.freezed.dart b/lib/application/report/report_bloc.freezed.dart new file mode 100644 index 0000000..8236fb6 --- /dev/null +++ b/lib/application/report/report_bloc.freezed.dart @@ -0,0 +1,616 @@ +// 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 'report_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ReportEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(int index, String title) menuChanged, + required TResult Function(DateTime startDate, DateTime endDate) dateChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int index, String title)? menuChanged, + TResult? Function(DateTime startDate, DateTime endDate)? dateChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int index, String title)? menuChanged, + TResult Function(DateTime startDate, DateTime endDate)? dateChanged, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_MenuChanged value) menuChanged, + required TResult Function(_DateChanged value) dateChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_MenuChanged value)? menuChanged, + TResult? Function(_DateChanged value)? dateChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_MenuChanged value)? menuChanged, + TResult Function(_DateChanged value)? dateChanged, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ReportEventCopyWith<$Res> { + factory $ReportEventCopyWith( + ReportEvent value, + $Res Function(ReportEvent) then, + ) = _$ReportEventCopyWithImpl<$Res, ReportEvent>; +} + +/// @nodoc +class _$ReportEventCopyWithImpl<$Res, $Val extends ReportEvent> + implements $ReportEventCopyWith<$Res> { + _$ReportEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ReportEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$MenuChangedImplCopyWith<$Res> { + factory _$$MenuChangedImplCopyWith( + _$MenuChangedImpl value, + $Res Function(_$MenuChangedImpl) then, + ) = __$$MenuChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({int index, String title}); +} + +/// @nodoc +class __$$MenuChangedImplCopyWithImpl<$Res> + extends _$ReportEventCopyWithImpl<$Res, _$MenuChangedImpl> + implements _$$MenuChangedImplCopyWith<$Res> { + __$$MenuChangedImplCopyWithImpl( + _$MenuChangedImpl _value, + $Res Function(_$MenuChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ReportEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? index = null, Object? title = null}) { + return _then( + _$MenuChangedImpl( + index: null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$MenuChangedImpl implements _MenuChanged { + const _$MenuChangedImpl({required this.index, required this.title}); + + @override + final int index; + @override + final String title; + + @override + String toString() { + return 'ReportEvent.menuChanged(index: $index, title: $title)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MenuChangedImpl && + (identical(other.index, index) || other.index == index) && + (identical(other.title, title) || other.title == title)); + } + + @override + int get hashCode => Object.hash(runtimeType, index, title); + + /// Create a copy of ReportEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MenuChangedImplCopyWith<_$MenuChangedImpl> get copyWith => + __$$MenuChangedImplCopyWithImpl<_$MenuChangedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int index, String title) menuChanged, + required TResult Function(DateTime startDate, DateTime endDate) dateChanged, + }) { + return menuChanged(index, title); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int index, String title)? menuChanged, + TResult? Function(DateTime startDate, DateTime endDate)? dateChanged, + }) { + return menuChanged?.call(index, title); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int index, String title)? menuChanged, + TResult Function(DateTime startDate, DateTime endDate)? dateChanged, + required TResult orElse(), + }) { + if (menuChanged != null) { + return menuChanged(index, title); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_MenuChanged value) menuChanged, + required TResult Function(_DateChanged value) dateChanged, + }) { + return menuChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_MenuChanged value)? menuChanged, + TResult? Function(_DateChanged value)? dateChanged, + }) { + return menuChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_MenuChanged value)? menuChanged, + TResult Function(_DateChanged value)? dateChanged, + required TResult orElse(), + }) { + if (menuChanged != null) { + return menuChanged(this); + } + return orElse(); + } +} + +abstract class _MenuChanged implements ReportEvent { + const factory _MenuChanged({ + required final int index, + required final String title, + }) = _$MenuChangedImpl; + + int get index; + String get title; + + /// Create a copy of ReportEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MenuChangedImplCopyWith<_$MenuChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DateChangedImplCopyWith<$Res> { + factory _$$DateChangedImplCopyWith( + _$DateChangedImpl value, + $Res Function(_$DateChangedImpl) then, + ) = __$$DateChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class __$$DateChangedImplCopyWithImpl<$Res> + extends _$ReportEventCopyWithImpl<$Res, _$DateChangedImpl> + implements _$$DateChangedImplCopyWith<$Res> { + __$$DateChangedImplCopyWithImpl( + _$DateChangedImpl _value, + $Res Function(_$DateChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ReportEvent + /// 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( + _$DateChangedImpl( + 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 _$DateChangedImpl implements _DateChanged { + const _$DateChangedImpl({required this.startDate, required this.endDate}); + + @override + final DateTime startDate; + @override + final DateTime endDate; + + @override + String toString() { + return 'ReportEvent.dateChanged(startDate: $startDate, endDate: $endDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DateChangedImpl && + (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 ReportEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DateChangedImplCopyWith<_$DateChangedImpl> get copyWith => + __$$DateChangedImplCopyWithImpl<_$DateChangedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int index, String title) menuChanged, + required TResult Function(DateTime startDate, DateTime endDate) dateChanged, + }) { + return dateChanged(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int index, String title)? menuChanged, + TResult? Function(DateTime startDate, DateTime endDate)? dateChanged, + }) { + return dateChanged?.call(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int index, String title)? menuChanged, + TResult Function(DateTime startDate, DateTime endDate)? dateChanged, + required TResult orElse(), + }) { + if (dateChanged != null) { + return dateChanged(startDate, endDate); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_MenuChanged value) menuChanged, + required TResult Function(_DateChanged value) dateChanged, + }) { + return dateChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_MenuChanged value)? menuChanged, + TResult? Function(_DateChanged value)? dateChanged, + }) { + return dateChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_MenuChanged value)? menuChanged, + TResult Function(_DateChanged value)? dateChanged, + required TResult orElse(), + }) { + if (dateChanged != null) { + return dateChanged(this); + } + return orElse(); + } +} + +abstract class _DateChanged implements ReportEvent { + const factory _DateChanged({ + required final DateTime startDate, + required final DateTime endDate, + }) = _$DateChangedImpl; + + DateTime get startDate; + DateTime get endDate; + + /// Create a copy of ReportEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DateChangedImplCopyWith<_$DateChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ReportState { + String get title => throw _privateConstructorUsedError; + DateTime get startDate => throw _privateConstructorUsedError; + DateTime get endDate => throw _privateConstructorUsedError; + String get rangeDateFormatted => throw _privateConstructorUsedError; + int get selectedMenu => throw _privateConstructorUsedError; + + /// Create a copy of ReportState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ReportStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ReportStateCopyWith<$Res> { + factory $ReportStateCopyWith( + ReportState value, + $Res Function(ReportState) then, + ) = _$ReportStateCopyWithImpl<$Res, ReportState>; + @useResult + $Res call({ + String title, + DateTime startDate, + DateTime endDate, + String rangeDateFormatted, + int selectedMenu, + }); +} + +/// @nodoc +class _$ReportStateCopyWithImpl<$Res, $Val extends ReportState> + implements $ReportStateCopyWith<$Res> { + _$ReportStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ReportState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? title = null, + Object? startDate = null, + Object? endDate = null, + Object? rangeDateFormatted = null, + Object? selectedMenu = null, + }) { + return _then( + _value.copyWith( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + 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, + rangeDateFormatted: null == rangeDateFormatted + ? _value.rangeDateFormatted + : rangeDateFormatted // ignore: cast_nullable_to_non_nullable + as String, + selectedMenu: null == selectedMenu + ? _value.selectedMenu + : selectedMenu // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ReportStateImplCopyWith<$Res> + implements $ReportStateCopyWith<$Res> { + factory _$$ReportStateImplCopyWith( + _$ReportStateImpl value, + $Res Function(_$ReportStateImpl) then, + ) = __$$ReportStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String title, + DateTime startDate, + DateTime endDate, + String rangeDateFormatted, + int selectedMenu, + }); +} + +/// @nodoc +class __$$ReportStateImplCopyWithImpl<$Res> + extends _$ReportStateCopyWithImpl<$Res, _$ReportStateImpl> + implements _$$ReportStateImplCopyWith<$Res> { + __$$ReportStateImplCopyWithImpl( + _$ReportStateImpl _value, + $Res Function(_$ReportStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ReportState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? title = null, + Object? startDate = null, + Object? endDate = null, + Object? rangeDateFormatted = null, + Object? selectedMenu = null, + }) { + return _then( + _$ReportStateImpl( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + 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, + rangeDateFormatted: null == rangeDateFormatted + ? _value.rangeDateFormatted + : rangeDateFormatted // ignore: cast_nullable_to_non_nullable + as String, + selectedMenu: null == selectedMenu + ? _value.selectedMenu + : selectedMenu // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$ReportStateImpl implements _ReportState { + _$ReportStateImpl({ + required this.title, + required this.startDate, + required this.endDate, + required this.rangeDateFormatted, + this.selectedMenu = 0, + }); + + @override + final String title; + @override + final DateTime startDate; + @override + final DateTime endDate; + @override + final String rangeDateFormatted; + @override + @JsonKey() + final int selectedMenu; + + @override + String toString() { + return 'ReportState(title: $title, startDate: $startDate, endDate: $endDate, rangeDateFormatted: $rangeDateFormatted, selectedMenu: $selectedMenu)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ReportStateImpl && + (identical(other.title, title) || other.title == title) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.rangeDateFormatted, rangeDateFormatted) || + other.rangeDateFormatted == rangeDateFormatted) && + (identical(other.selectedMenu, selectedMenu) || + other.selectedMenu == selectedMenu)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + title, + startDate, + endDate, + rangeDateFormatted, + selectedMenu, + ); + + /// Create a copy of ReportState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ReportStateImplCopyWith<_$ReportStateImpl> get copyWith => + __$$ReportStateImplCopyWithImpl<_$ReportStateImpl>(this, _$identity); +} + +abstract class _ReportState implements ReportState { + factory _ReportState({ + required final String title, + required final DateTime startDate, + required final DateTime endDate, + required final String rangeDateFormatted, + final int selectedMenu, + }) = _$ReportStateImpl; + + @override + String get title; + @override + DateTime get startDate; + @override + DateTime get endDate; + @override + String get rangeDateFormatted; + @override + int get selectedMenu; + + /// Create a copy of ReportState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ReportStateImplCopyWith<_$ReportStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/report/report_event.dart b/lib/application/report/report_event.dart new file mode 100644 index 0000000..d45574d --- /dev/null +++ b/lib/application/report/report_event.dart @@ -0,0 +1,13 @@ +part of 'report_bloc.dart'; + +@freezed +class ReportEvent with _$ReportEvent { + const factory ReportEvent.menuChanged({ + required int index, + required String title, + }) = _MenuChanged; + const factory ReportEvent.dateChanged({ + required DateTime startDate, + required DateTime endDate, + }) = _DateChanged; +} diff --git a/lib/application/report/report_state.dart b/lib/application/report/report_state.dart new file mode 100644 index 0000000..d4f8e5b --- /dev/null +++ b/lib/application/report/report_state.dart @@ -0,0 +1,20 @@ +part of 'report_bloc.dart'; + +@freezed +class ReportState with _$ReportState { + factory ReportState({ + required String title, + required DateTime startDate, + required DateTime endDate, + required String rangeDateFormatted, + @Default(0) int selectedMenu, + }) = _ReportState; + + factory ReportState.initial() => ReportState( + title: 'Ringkasan Laporan Penjualan', + startDate: DateTime.now(), + endDate: DateTime.now(), + rangeDateFormatted: + '${DateTime.now().toFormattedDate()} - ${DateTime.now().toFormattedDate()}', + ); +} diff --git a/lib/common/data/report_menu.dart b/lib/common/data/report_menu.dart new file mode 100644 index 0000000..092de06 --- /dev/null +++ b/lib/common/data/report_menu.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class ReportMenu { + ReportMenu({ + required this.index, + required this.title, + required this.subtitle, + required this.icons, + }); + + final int index; + final String title; + final String subtitle; + final IconData icons; +} + +List reportMenus = [ + ReportMenu( + index: 0, + title: 'Ringkasan Laporan Penjualan', + subtitle: 'Ringkasan total penjualan dalam periode tertentu.', + icons: Icons.insert_drive_file_outlined, + ), + ReportMenu( + index: 1, + title: 'Laporan Transaksi', + subtitle: + 'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.', + icons: Icons.insert_drive_file_outlined, + ), + ReportMenu( + index: 2, + title: 'Laporan Penjualan Item', + subtitle: 'Laporan penjualan berdasarkan masing-masing item atau produk.', + icons: Icons.inventory_2_outlined, + ), + ReportMenu( + index: 3, + title: 'Laporan Penjualan Produk', + subtitle: 'Laporan penjualan berdasarkan masing-masing produk.', + icons: Icons.bar_chart_outlined, + ), + ReportMenu( + index: 4, + title: 'Laporan Metode Pembayaran', + subtitle: 'Laporan metode pembayaran yang digunakan.', + icons: Icons.payment_outlined, + ), + ReportMenu( + index: 5, + title: 'Laporan Untung Rugi', + subtitle: 'Laporan untung rugi penjualan.', + icons: Icons.trending_down_outlined, + ), + ReportMenu( + index: 6, + title: 'Laporan Inventori', + subtitle: 'Laporan inventori produk', + icons: Icons.archive_outlined, + ), + ReportMenu( + index: 7, + title: 'Laporan Kategori', + subtitle: 'Laporan kategori produk', + icons: Icons.category_outlined, + ), +]; diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 3a8a749..29d8257 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -30,6 +30,8 @@ import 'package:apskel_pos_flutter_v2/application/payment_method/payment_method_ as _i952; import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart' as _i13; +import 'package:apskel_pos_flutter_v2/application/report/report_bloc.dart' + as _i257; import 'package:apskel_pos_flutter_v2/application/split_bill/split_bill_form/split_bill_form_bloc.dart' as _i334; import 'package:apskel_pos_flutter_v2/application/sync/sync_bloc.dart' as _i741; @@ -129,6 +131,7 @@ extension GetItInjectableX on _i174.GetIt { ); gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc()); gh.factory<_i334.SplitBillFormBloc>(() => _i334.SplitBillFormBloc()); + gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc()); gh.singleton<_i487.DatabaseHelper>(() => databaseDi.databaseHelper); gh.lazySingleton<_i361.Dio>(() => dioDi.dio); gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter); diff --git a/lib/presentation/components/button/button.dart b/lib/presentation/components/button/button.dart index adcc7ca..8386bd0 100644 --- a/lib/presentation/components/button/button.dart +++ b/lib/presentation/components/button/button.dart @@ -5,3 +5,4 @@ import '../../../common/theme/theme.dart'; import '../spaces/space.dart'; part 'elevated_button.dart'; +part 'icon_button.dart'; diff --git a/lib/presentation/components/button/icon_button.dart b/lib/presentation/components/button/icon_button.dart new file mode 100644 index 0000000..8fd8a95 --- /dev/null +++ b/lib/presentation/components/button/icon_button.dart @@ -0,0 +1,22 @@ +part of 'button.dart'; + +class AppIconButton extends StatelessWidget { + final IconData icons; + final Function()? onTap; + const AppIconButton({super.key, required this.icons, required this.onTap}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + border: Border.all(color: AppColor.border), + ), + child: Icon(icons, color: AppColor.primary, size: 28), + ), + ); + } +} diff --git a/lib/presentation/components/picker/date_range_picker.dart b/lib/presentation/components/picker/date_range_picker.dart index 66b2ca6..81d671b 100644 --- a/lib/presentation/components/picker/date_range_picker.dart +++ b/lib/presentation/components/picker/date_range_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; import '../../../common/theme/theme.dart'; +import '../spaces/space.dart'; class DateRangePickerModal { static Future show({ @@ -217,42 +218,7 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> child: SingleChildScrollView( child: Column( children: [ - // Selection Info - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: widget.primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: widget.primaryColor.withOpacity(0.2), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Tanggal Terpilih:', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: widget.primaryColor, - ), - ), - const SizedBox(height: 4), - Text( - _getSelectionText(), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - ], - ), - ), - + SpaceHeight(16), // Date Picker Padding( padding: const EdgeInsets.symmetric( @@ -320,6 +286,41 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> ), ), ), + // Selection Info + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: widget.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: widget.primaryColor.withOpacity(0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tanggal Terpilih:', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: widget.primaryColor, + ), + ), + const SizedBox(height: 4), + Text( + _getSelectionText(), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), + ), ], ), ), diff --git a/lib/presentation/pages/main/pages/report/report_page.dart b/lib/presentation/pages/main/pages/report/report_page.dart index 04f367e..c5768a5 100644 --- a/lib/presentation/pages/main/pages/report/report_page.dart +++ b/lib/presentation/pages/main/pages/report/report_page.dart @@ -1,12 +1,123 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/report/report_bloc.dart'; +import '../../../../../common/data/report_menu.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../../injection.dart'; +import '../../../../components/button/button.dart'; +import '../../../../components/page/page_title.dart'; +import '../../../../components/picker/date_range_picker.dart'; +import '../../../../components/spaces/space.dart'; +import '../../../../router/app_router.gr.dart'; +import 'widgets/report_menu_card.dart'; @RoutePage() -class ReportPage extends StatelessWidget { +class ReportPage extends StatelessWidget implements AutoRouteWrapper { const ReportPage({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('Report Page')); + return Scaffold( + backgroundColor: AppColor.white, + body: SafeArea( + child: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + PageTitle( + title: 'Report', + subtitle: state.rangeDateFormatted, + actionWidget: [ + AppIconButton( + icons: Icons.calendar_month_outlined, + onTap: () { + DateRangePickerModal.show( + context: context, + initialEndDate: state.endDate, + initialStartDate: state.startDate, + primaryColor: AppColor.primary, + onChanged: (startDate, endDate) { + context.read().add( + ReportEvent.dateChanged( + startDate: startDate!, + endDate: endDate!, + ), + ); + }, + ); + }, + ), + SpaceWidth(8), + AppIconButton(icons: Icons.download_outlined, onTap: () {}), + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + flex: 2, + child: Material( + color: AppColor.white, + child: SingleChildScrollView( + child: Column( + children: List.generate( + reportMenus.length, + (index) => ReportMenuCard( + menu: reportMenus[index], + isActive: + reportMenus[index].index == + state.selectedMenu, + onTap: () { + if (reportMenus[index].index == 1) { + context.router.push( + OrderRoute(status: 'completed'), + ); + } else { + context.read().add( + ReportEvent.menuChanged( + index: index, + title: reportMenus[index].title, + ), + ); + } + }, + ), + ), + ), + ), + ), + ), + Expanded( + flex: 4, + child: Material( + color: AppColor.background, + child: switch (state.selectedMenu) { + 0 => Text(state.title), + 1 => Text(state.title), + 2 => Text(state.title), + 3 => Text(state.title), + 4 => Text(state.title), + 5 => Text(state.title), + 6 => Text(state.title), + 7 => Text(state.title), + _ => Container(), + }, + ), + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); } + + @override + Widget wrappedRoute(BuildContext context) => + BlocProvider(create: (context) => getIt(), child: this); } diff --git a/lib/presentation/pages/main/pages/report/widgets/report_menu_card.dart b/lib/presentation/pages/main/pages/report/widgets/report_menu_card.dart new file mode 100644 index 0000000..5ace3e1 --- /dev/null +++ b/lib/presentation/pages/main/pages/report/widgets/report_menu_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/data/report_menu.dart'; +import '../../../../../../common/theme/theme.dart'; +import '../../../../../components/spaces/space.dart'; + +class ReportMenuCard extends StatelessWidget { + final ReportMenu menu; + final bool isActive; + final Function() onTap; + const ReportMenuCard({ + super.key, + required this.menu, + required this.isActive, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: isActive ? AppColor.primary : Colors.transparent, + width: 4.0, + ), + ), + ), + child: Row( + children: [ + Icon( + menu.icons, + size: 24.0, + color: isActive ? AppColor.primary : AppColor.textSecondary, + ), + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + menu.title, + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + SpaceHeight(4.0), + Text( + menu.subtitle, + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 799915a..6c4dd80 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -336,7 +336,7 @@ class ReportRoute extends _i20.PageRouteInfo { static _i20.PageInfo page = _i20.PageInfo( name, builder: (data) { - return const _i10.ReportPage(); + return _i20.WrappedRoute(child: const _i10.ReportPage()); }, ); }