Compare commits
19
Commits
dev
..
3ae2e93531
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ae2e93531 | ||
|
|
a182a3504c | ||
|
|
88972b28b5 | ||
|
|
2c8e9a3fb4 | ||
|
|
086c17a217 | ||
|
|
9a7681680d | ||
|
|
823e009121 | ||
|
|
602647ff26 | ||
|
|
d9a553708b | ||
|
|
aaa8eba31b | ||
|
|
31a82e14cb | ||
|
|
e88eff28f0 | ||
|
|
720c63b6c6 | ||
|
|
ca2cc8bf6b | ||
|
|
d56c3f89db | ||
|
|
f7f5682924 | ||
|
|
b51749a01b | ||
|
|
5fd870c507 | ||
|
|
d8d8fd9d16 |
@@ -0,0 +1,50 @@
|
|||||||
|
name: Build iOS IPA
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-ios:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: "3.41.9"
|
||||||
|
|
||||||
|
- name: Upgrade Firebase for Xcode 26 compatibility
|
||||||
|
run: |
|
||||||
|
sed -i '' 's/firebase_core: 2.32.0/firebase_core: ^3.13.1/' pubspec.yaml
|
||||||
|
sed -i '' 's/firebase_messaging: 14.7.10/firebase_messaging: ^15.2.5/' pubspec.yaml
|
||||||
|
sed -i '' 's/firebase_crashlytics: \^3\.[0-9]*\.[0-9]*/firebase_crashlytics: ^5.2.0/' pubspec.yaml
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Install CocoaPods
|
||||||
|
run: |
|
||||||
|
cd ios
|
||||||
|
rm -f Podfile.lock
|
||||||
|
pod install --repo-update
|
||||||
|
|
||||||
|
- name: Build iOS (no codesign)
|
||||||
|
run: flutter build ios --release --no-codesign
|
||||||
|
|
||||||
|
- name: Create IPA from app bundle
|
||||||
|
run: |
|
||||||
|
mkdir -p build/ios/ipa
|
||||||
|
cd build/ios/Release-iphoneos
|
||||||
|
mkdir -p Payload
|
||||||
|
cp -r Runner.app Payload/
|
||||||
|
zip -r ../ipa/Runner.ipa Payload
|
||||||
|
echo "IPA created at build/ios/ipa/Runner.ipa"
|
||||||
|
|
||||||
|
- name: Upload IPA Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ios-ipa
|
||||||
|
path: build/ios/ipa/Runner.ipa
|
||||||
@@ -39,5 +39,9 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
|
||||||
|
config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -176,6 +176,6 @@ SPEC CHECKSUMS:
|
|||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
|
|
||||||
PODFILE CHECKSUM: e30f02f9d1c72c47bb6344a0a748c9d268180865
|
PODFILE CHECKSUM: ecb39f0da990336cc02cd0b433a02558e58ace35
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
2edd7a931624573eff7b4280d22a7f4e
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import '../../../domain/analytic/analytic.dart';
|
||||||
|
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||||
|
|
||||||
|
part 'purchasing_analytic_loader_event.dart';
|
||||||
|
part 'purchasing_analytic_loader_state.dart';
|
||||||
|
part 'purchasing_analytic_loader_bloc.freezed.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class PurchasingAnalyticLoaderBloc
|
||||||
|
extends Bloc<PurchasingAnalyticLoaderEvent, PurchasingAnalyticLoaderState> {
|
||||||
|
final IAnalyticRepository _analyticRepository;
|
||||||
|
|
||||||
|
PurchasingAnalyticLoaderBloc(this._analyticRepository)
|
||||||
|
: super(PurchasingAnalyticLoaderState.initial()) {
|
||||||
|
on<PurchasingAnalyticLoaderEvent>(_onEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onEvent(
|
||||||
|
PurchasingAnalyticLoaderEvent event,
|
||||||
|
Emitter<PurchasingAnalyticLoaderState> emit,
|
||||||
|
) {
|
||||||
|
return event.map(
|
||||||
|
rangeDateChanged: (e) async {
|
||||||
|
emit(state.copyWith(dateFrom: e.dateFrom, dateTo: e.dateTo));
|
||||||
|
},
|
||||||
|
outletChanged: (e) async {
|
||||||
|
emit(state.copyWith(outletId: e.outletId));
|
||||||
|
},
|
||||||
|
fetched: (e) async {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isFetching: true,
|
||||||
|
failureOptionPurchasing: none(),
|
||||||
|
));
|
||||||
|
|
||||||
|
final result = await _analyticRepository.getPurchasing(
|
||||||
|
dateFrom: state.dateFrom,
|
||||||
|
dateTo: state.dateTo,
|
||||||
|
outletId: state.outletId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final newState = result.fold(
|
||||||
|
(f) => state.copyWith(failureOptionPurchasing: optionOf(f)),
|
||||||
|
(purchasing) => state.copyWith(purchasing: purchasing),
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(newState.copyWith(isFetching: false));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+818
@@ -0,0 +1,818 @@
|
|||||||
|
// 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 'purchasing_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 _$PurchasingAnalyticLoaderEvent {
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||||
|
rangeDateChanged,
|
||||||
|
required TResult Function(String? outletId) outletChanged,
|
||||||
|
required TResult Function() fetched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult? Function(String? outletId)? outletChanged,
|
||||||
|
TResult? Function()? fetched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult Function(String? outletId)? outletChanged,
|
||||||
|
TResult Function()? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||||
|
required TResult Function(_OutletChanged value) outletChanged,
|
||||||
|
required TResult Function(_Fetched value) fetched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult? Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult? Function(_Fetched value)? fetched,
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult Function(_Fetched value)? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $PurchasingAnalyticLoaderEventCopyWith<$Res> {
|
||||||
|
factory $PurchasingAnalyticLoaderEventCopyWith(
|
||||||
|
PurchasingAnalyticLoaderEvent value,
|
||||||
|
$Res Function(PurchasingAnalyticLoaderEvent) then,
|
||||||
|
) =
|
||||||
|
_$PurchasingAnalyticLoaderEventCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
PurchasingAnalyticLoaderEvent
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$PurchasingAnalyticLoaderEventCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
$Val extends PurchasingAnalyticLoaderEvent
|
||||||
|
>
|
||||||
|
implements $PurchasingAnalyticLoaderEventCopyWith<$Res> {
|
||||||
|
_$PurchasingAnalyticLoaderEventCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// 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
|
||||||
|
_$PurchasingAnalyticLoaderEventCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
_$RangeDateChangedImpl
|
||||||
|
>
|
||||||
|
implements _$$RangeDateChangedImplCopyWith<$Res> {
|
||||||
|
__$$RangeDateChangedImplCopyWithImpl(
|
||||||
|
_$RangeDateChangedImpl _value,
|
||||||
|
$Res Function(_$RangeDateChangedImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// 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 'PurchasingAnalyticLoaderEvent.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 PurchasingAnalyticLoaderEvent
|
||||||
|
/// 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<TResult extends Object?>({
|
||||||
|
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||||
|
rangeDateChanged,
|
||||||
|
required TResult Function(String? outletId) outletChanged,
|
||||||
|
required TResult Function() fetched,
|
||||||
|
}) {
|
||||||
|
return rangeDateChanged(dateFrom, dateTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult? Function(String? outletId)? outletChanged,
|
||||||
|
TResult? Function()? fetched,
|
||||||
|
}) {
|
||||||
|
return rangeDateChanged?.call(dateFrom, dateTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult Function(String? outletId)? outletChanged,
|
||||||
|
TResult Function()? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (rangeDateChanged != null) {
|
||||||
|
return rangeDateChanged(dateFrom, dateTo);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||||
|
required TResult Function(_OutletChanged value) outletChanged,
|
||||||
|
required TResult Function(_Fetched value) fetched,
|
||||||
|
}) {
|
||||||
|
return rangeDateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult? Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult? Function(_Fetched value)? fetched,
|
||||||
|
}) {
|
||||||
|
return rangeDateChanged?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult Function(_Fetched value)? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (rangeDateChanged != null) {
|
||||||
|
return rangeDateChanged(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _RangeDateChanged implements PurchasingAnalyticLoaderEvent {
|
||||||
|
const factory _RangeDateChanged(
|
||||||
|
final DateTime dateFrom,
|
||||||
|
final DateTime dateTo,
|
||||||
|
) = _$RangeDateChangedImpl;
|
||||||
|
|
||||||
|
DateTime get dateFrom;
|
||||||
|
DateTime get dateTo;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// 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 _$$OutletChangedImplCopyWith<$Res> {
|
||||||
|
factory _$$OutletChangedImplCopyWith(
|
||||||
|
_$OutletChangedImpl value,
|
||||||
|
$Res Function(_$OutletChangedImpl) then,
|
||||||
|
) = __$$OutletChangedImplCopyWithImpl<$Res>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String? outletId});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$OutletChangedImplCopyWithImpl<$Res>
|
||||||
|
extends
|
||||||
|
_$PurchasingAnalyticLoaderEventCopyWithImpl<$Res, _$OutletChangedImpl>
|
||||||
|
implements _$$OutletChangedImplCopyWith<$Res> {
|
||||||
|
__$$OutletChangedImplCopyWithImpl(
|
||||||
|
_$OutletChangedImpl _value,
|
||||||
|
$Res Function(_$OutletChangedImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({Object? outletId = freezed}) {
|
||||||
|
return _then(
|
||||||
|
_$OutletChangedImpl(
|
||||||
|
freezed == outletId
|
||||||
|
? _value.outletId
|
||||||
|
: outletId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$OutletChangedImpl implements _OutletChanged {
|
||||||
|
const _$OutletChangedImpl(this.outletId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? outletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PurchasingAnalyticLoaderEvent.outletChanged(outletId: $outletId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$OutletChangedImpl &&
|
||||||
|
(identical(other.outletId, outletId) ||
|
||||||
|
other.outletId == outletId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, outletId);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$OutletChangedImplCopyWith<_$OutletChangedImpl> get copyWith =>
|
||||||
|
__$$OutletChangedImplCopyWithImpl<_$OutletChangedImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||||
|
rangeDateChanged,
|
||||||
|
required TResult Function(String? outletId) outletChanged,
|
||||||
|
required TResult Function() fetched,
|
||||||
|
}) {
|
||||||
|
return outletChanged(outletId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult? Function(String? outletId)? outletChanged,
|
||||||
|
TResult? Function()? fetched,
|
||||||
|
}) {
|
||||||
|
return outletChanged?.call(outletId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult Function(String? outletId)? outletChanged,
|
||||||
|
TResult Function()? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (outletChanged != null) {
|
||||||
|
return outletChanged(outletId);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||||
|
required TResult Function(_OutletChanged value) outletChanged,
|
||||||
|
required TResult Function(_Fetched value) fetched,
|
||||||
|
}) {
|
||||||
|
return outletChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult? Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult? Function(_Fetched value)? fetched,
|
||||||
|
}) {
|
||||||
|
return outletChanged?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult Function(_Fetched value)? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (outletChanged != null) {
|
||||||
|
return outletChanged(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _OutletChanged implements PurchasingAnalyticLoaderEvent {
|
||||||
|
const factory _OutletChanged(final String? outletId) = _$OutletChangedImpl;
|
||||||
|
|
||||||
|
String? get outletId;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$OutletChangedImplCopyWith<_$OutletChangedImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||||
|
factory _$$FetchedImplCopyWith(
|
||||||
|
_$FetchedImpl value,
|
||||||
|
$Res Function(_$FetchedImpl) then,
|
||||||
|
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$FetchedImplCopyWithImpl<$Res>
|
||||||
|
extends _$PurchasingAnalyticLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||||
|
implements _$$FetchedImplCopyWith<$Res> {
|
||||||
|
__$$FetchedImplCopyWithImpl(
|
||||||
|
_$FetchedImpl _value,
|
||||||
|
$Res Function(_$FetchedImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$FetchedImpl implements _Fetched {
|
||||||
|
const _$FetchedImpl();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PurchasingAnalyticLoaderEvent.fetched()';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType && other is _$FetchedImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult when<TResult extends Object?>({
|
||||||
|
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||||
|
rangeDateChanged,
|
||||||
|
required TResult Function(String? outletId) outletChanged,
|
||||||
|
required TResult Function() fetched,
|
||||||
|
}) {
|
||||||
|
return fetched();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult? Function(String? outletId)? outletChanged,
|
||||||
|
TResult? Function()? fetched,
|
||||||
|
}) {
|
||||||
|
return fetched?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
|
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||||
|
TResult Function(String? outletId)? outletChanged,
|
||||||
|
TResult Function()? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (fetched != null) {
|
||||||
|
return fetched();
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult map<TResult extends Object?>({
|
||||||
|
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||||
|
required TResult Function(_OutletChanged value) outletChanged,
|
||||||
|
required TResult Function(_Fetched value) fetched,
|
||||||
|
}) {
|
||||||
|
return fetched(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult? mapOrNull<TResult extends Object?>({
|
||||||
|
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult? Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult? Function(_Fetched value)? fetched,
|
||||||
|
}) {
|
||||||
|
return fetched?.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@optionalTypeArgs
|
||||||
|
TResult maybeMap<TResult extends Object?>({
|
||||||
|
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||||
|
TResult Function(_OutletChanged value)? outletChanged,
|
||||||
|
TResult Function(_Fetched value)? fetched,
|
||||||
|
required TResult orElse(),
|
||||||
|
}) {
|
||||||
|
if (fetched != null) {
|
||||||
|
return fetched(this);
|
||||||
|
}
|
||||||
|
return orElse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Fetched implements PurchasingAnalyticLoaderEvent {
|
||||||
|
const factory _Fetched() = _$FetchedImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$PurchasingAnalyticLoaderState {
|
||||||
|
PurchasingAnalytic get purchasing => throw _privateConstructorUsedError;
|
||||||
|
Option<AnalyticFailure> get failureOptionPurchasing =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
bool get isFetching => throw _privateConstructorUsedError;
|
||||||
|
DateTime get dateFrom => throw _privateConstructorUsedError;
|
||||||
|
DateTime get dateTo => throw _privateConstructorUsedError;
|
||||||
|
String? get outletId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$PurchasingAnalyticLoaderStateCopyWith<PurchasingAnalyticLoaderState>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $PurchasingAnalyticLoaderStateCopyWith<$Res> {
|
||||||
|
factory $PurchasingAnalyticLoaderStateCopyWith(
|
||||||
|
PurchasingAnalyticLoaderState value,
|
||||||
|
$Res Function(PurchasingAnalyticLoaderState) then,
|
||||||
|
) =
|
||||||
|
_$PurchasingAnalyticLoaderStateCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
PurchasingAnalyticLoaderState
|
||||||
|
>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
PurchasingAnalytic purchasing,
|
||||||
|
Option<AnalyticFailure> failureOptionPurchasing,
|
||||||
|
bool isFetching,
|
||||||
|
DateTime dateFrom,
|
||||||
|
DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
});
|
||||||
|
|
||||||
|
$PurchasingAnalyticCopyWith<$Res> get purchasing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$PurchasingAnalyticLoaderStateCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
$Val extends PurchasingAnalyticLoaderState
|
||||||
|
>
|
||||||
|
implements $PurchasingAnalyticLoaderStateCopyWith<$Res> {
|
||||||
|
_$PurchasingAnalyticLoaderStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? purchasing = null,
|
||||||
|
Object? failureOptionPurchasing = null,
|
||||||
|
Object? isFetching = null,
|
||||||
|
Object? dateFrom = null,
|
||||||
|
Object? dateTo = null,
|
||||||
|
Object? outletId = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
purchasing: null == purchasing
|
||||||
|
? _value.purchasing
|
||||||
|
: purchasing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as PurchasingAnalytic,
|
||||||
|
failureOptionPurchasing: null == failureOptionPurchasing
|
||||||
|
? _value.failureOptionPurchasing
|
||||||
|
: failureOptionPurchasing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Option<AnalyticFailure>,
|
||||||
|
isFetching: null == isFetching
|
||||||
|
? _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,
|
||||||
|
outletId: freezed == outletId
|
||||||
|
? _value.outletId
|
||||||
|
: outletId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$PurchasingAnalyticCopyWith<$Res> get purchasing {
|
||||||
|
return $PurchasingAnalyticCopyWith<$Res>(_value.purchasing, (value) {
|
||||||
|
return _then(_value.copyWith(purchasing: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$PurchasingAnalyticLoaderStateImplCopyWith<$Res>
|
||||||
|
implements $PurchasingAnalyticLoaderStateCopyWith<$Res> {
|
||||||
|
factory _$$PurchasingAnalyticLoaderStateImplCopyWith(
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl value,
|
||||||
|
$Res Function(_$PurchasingAnalyticLoaderStateImpl) then,
|
||||||
|
) = __$$PurchasingAnalyticLoaderStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
PurchasingAnalytic purchasing,
|
||||||
|
Option<AnalyticFailure> failureOptionPurchasing,
|
||||||
|
bool isFetching,
|
||||||
|
DateTime dateFrom,
|
||||||
|
DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$PurchasingAnalyticCopyWith<$Res> get purchasing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$PurchasingAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||||
|
extends
|
||||||
|
_$PurchasingAnalyticLoaderStateCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl
|
||||||
|
>
|
||||||
|
implements _$$PurchasingAnalyticLoaderStateImplCopyWith<$Res> {
|
||||||
|
__$$PurchasingAnalyticLoaderStateImplCopyWithImpl(
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl _value,
|
||||||
|
$Res Function(_$PurchasingAnalyticLoaderStateImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? purchasing = null,
|
||||||
|
Object? failureOptionPurchasing = null,
|
||||||
|
Object? isFetching = null,
|
||||||
|
Object? dateFrom = null,
|
||||||
|
Object? dateTo = null,
|
||||||
|
Object? outletId = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl(
|
||||||
|
purchasing: null == purchasing
|
||||||
|
? _value.purchasing
|
||||||
|
: purchasing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as PurchasingAnalytic,
|
||||||
|
failureOptionPurchasing: null == failureOptionPurchasing
|
||||||
|
? _value.failureOptionPurchasing
|
||||||
|
: failureOptionPurchasing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Option<AnalyticFailure>,
|
||||||
|
isFetching: null == isFetching
|
||||||
|
? _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,
|
||||||
|
outletId: freezed == outletId
|
||||||
|
? _value.outletId
|
||||||
|
: outletId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$PurchasingAnalyticLoaderStateImpl
|
||||||
|
implements _PurchasingAnalyticLoaderState {
|
||||||
|
const _$PurchasingAnalyticLoaderStateImpl({
|
||||||
|
required this.purchasing,
|
||||||
|
required this.failureOptionPurchasing,
|
||||||
|
this.isFetching = false,
|
||||||
|
required this.dateFrom,
|
||||||
|
required this.dateTo,
|
||||||
|
this.outletId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final PurchasingAnalytic purchasing;
|
||||||
|
@override
|
||||||
|
final Option<AnalyticFailure> failureOptionPurchasing;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool isFetching;
|
||||||
|
@override
|
||||||
|
final DateTime dateFrom;
|
||||||
|
@override
|
||||||
|
final DateTime dateTo;
|
||||||
|
@override
|
||||||
|
final String? outletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PurchasingAnalyticLoaderState(purchasing: $purchasing, failureOptionPurchasing: $failureOptionPurchasing, isFetching: $isFetching, dateFrom: $dateFrom, dateTo: $dateTo, outletId: $outletId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$PurchasingAnalyticLoaderStateImpl &&
|
||||||
|
(identical(other.purchasing, purchasing) ||
|
||||||
|
other.purchasing == purchasing) &&
|
||||||
|
(identical(
|
||||||
|
other.failureOptionPurchasing,
|
||||||
|
failureOptionPurchasing,
|
||||||
|
) ||
|
||||||
|
other.failureOptionPurchasing == failureOptionPurchasing) &&
|
||||||
|
(identical(other.isFetching, isFetching) ||
|
||||||
|
other.isFetching == isFetching) &&
|
||||||
|
(identical(other.dateFrom, dateFrom) ||
|
||||||
|
other.dateFrom == dateFrom) &&
|
||||||
|
(identical(other.dateTo, dateTo) || other.dateTo == dateTo) &&
|
||||||
|
(identical(other.outletId, outletId) ||
|
||||||
|
other.outletId == outletId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
purchasing,
|
||||||
|
failureOptionPurchasing,
|
||||||
|
isFetching,
|
||||||
|
dateFrom,
|
||||||
|
dateTo,
|
||||||
|
outletId,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$PurchasingAnalyticLoaderStateImplCopyWith<
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl
|
||||||
|
>
|
||||||
|
get copyWith =>
|
||||||
|
__$$PurchasingAnalyticLoaderStateImplCopyWithImpl<
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl
|
||||||
|
>(this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _PurchasingAnalyticLoaderState
|
||||||
|
implements PurchasingAnalyticLoaderState {
|
||||||
|
const factory _PurchasingAnalyticLoaderState({
|
||||||
|
required final PurchasingAnalytic purchasing,
|
||||||
|
required final Option<AnalyticFailure> failureOptionPurchasing,
|
||||||
|
final bool isFetching,
|
||||||
|
required final DateTime dateFrom,
|
||||||
|
required final DateTime dateTo,
|
||||||
|
final String? outletId,
|
||||||
|
}) = _$PurchasingAnalyticLoaderStateImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PurchasingAnalytic get purchasing;
|
||||||
|
@override
|
||||||
|
Option<AnalyticFailure> get failureOptionPurchasing;
|
||||||
|
@override
|
||||||
|
bool get isFetching;
|
||||||
|
@override
|
||||||
|
DateTime get dateFrom;
|
||||||
|
@override
|
||||||
|
DateTime get dateTo;
|
||||||
|
@override
|
||||||
|
String? get outletId;
|
||||||
|
|
||||||
|
/// Create a copy of PurchasingAnalyticLoaderState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$PurchasingAnalyticLoaderStateImplCopyWith<
|
||||||
|
_$PurchasingAnalyticLoaderStateImpl
|
||||||
|
>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
part of 'purchasing_analytic_loader_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticLoaderEvent with _$PurchasingAnalyticLoaderEvent {
|
||||||
|
const factory PurchasingAnalyticLoaderEvent.rangeDateChanged(
|
||||||
|
DateTime dateFrom,
|
||||||
|
DateTime dateTo,
|
||||||
|
) = _RangeDateChanged;
|
||||||
|
|
||||||
|
const factory PurchasingAnalyticLoaderEvent.outletChanged(
|
||||||
|
String? outletId,
|
||||||
|
) = _OutletChanged;
|
||||||
|
|
||||||
|
const factory PurchasingAnalyticLoaderEvent.fetched() = _Fetched;
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
part of 'purchasing_analytic_loader_bloc.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticLoaderState with _$PurchasingAnalyticLoaderState {
|
||||||
|
const factory PurchasingAnalyticLoaderState({
|
||||||
|
required PurchasingAnalytic purchasing,
|
||||||
|
required Option<AnalyticFailure> failureOptionPurchasing,
|
||||||
|
@Default(false) bool isFetching,
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
}) = _PurchasingAnalyticLoaderState;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticLoaderState.initial() =>
|
||||||
|
PurchasingAnalyticLoaderState(
|
||||||
|
purchasing: PurchasingAnalytic.empty(),
|
||||||
|
failureOptionPurchasing: none(),
|
||||||
|
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||||
|
dateTo: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -65,5 +65,8 @@ class ThemeApp {
|
|||||||
),
|
),
|
||||||
iconTheme: const IconThemeData(color: AppColor.white),
|
iconTheme: const IconThemeData(color: AppColor.white),
|
||||||
),
|
),
|
||||||
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
|
backgroundColor: AppColor.white,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class ApiPath {
|
|||||||
static const String productAnalytic = '/api/v1/analytics/products';
|
static const String productAnalytic = '/api/v1/analytics/products';
|
||||||
static const String paymentMethodAnalytic =
|
static const String paymentMethodAnalytic =
|
||||||
'/api/v1/analytics/paymentMethods';
|
'/api/v1/analytics/paymentMethods';
|
||||||
|
static const String purchasingAnalytic = '/api/v1/analytics/purchasing';
|
||||||
|
|
||||||
// Inventory
|
// Inventory
|
||||||
static const String inventoryReportDetail =
|
static const String inventoryReportDetail =
|
||||||
|
|||||||
@@ -101,17 +101,26 @@ class FcmService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 7. Print FCM token for debugging
|
// 7. Print FCM token for debugging
|
||||||
|
try {
|
||||||
final token = await getToken();
|
final token = await getToken();
|
||||||
debugPrint('[FCM] Token: $token');
|
debugPrint('[FCM] Token: $token');
|
||||||
|
} catch (e) {
|
||||||
|
// Simulator atau APNs belum dikonfigurasi — skip token fetch
|
||||||
|
debugPrint('[FCM] Token unavailable: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestPermission() async {
|
Future<void> _requestPermission() async {
|
||||||
|
try {
|
||||||
final settings = await _messaging.requestPermission(
|
final settings = await _messaging.requestPermission(
|
||||||
alert: true,
|
alert: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
sound: true,
|
sound: true,
|
||||||
);
|
);
|
||||||
debugPrint('[FCM] Permission status: ${settings.authorizationStatus}');
|
debugPrint('[FCM] Permission status: ${settings.authorizationStatus}');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[FCM] Permission request failed: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setupLocalNotifications() async {
|
Future<void> _setupLocalNotifications() async {
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ part 'entities/inventory_analytic_entity.dart';
|
|||||||
part 'entities/dashboard_analytic_entity.dart';
|
part 'entities/dashboard_analytic_entity.dart';
|
||||||
part 'entities/product_analytic_entity.dart';
|
part 'entities/product_analytic_entity.dart';
|
||||||
part 'entities/payment_method_analytic_entity.dart';
|
part 'entities/payment_method_analytic_entity.dart';
|
||||||
|
part 'entities/purchasing_analytic_entity.dart';
|
||||||
part 'failures/analytic_failure.dart';
|
part 'failures/analytic_failure.dart';
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
|||||||
|
part of '../analytic.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalytic with _$PurchasingAnalytic {
|
||||||
|
const factory PurchasingAnalytic({
|
||||||
|
required String organizationId,
|
||||||
|
required String outletId,
|
||||||
|
required String outletName,
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
required String groupBy,
|
||||||
|
required PurchasingAnalyticSummary summary,
|
||||||
|
required List<PurchasingAnalyticData> data,
|
||||||
|
required List<PurchasingIngredientData> ingredientData,
|
||||||
|
required List<PurchasingVendorData> vendorData,
|
||||||
|
}) = _PurchasingAnalytic;
|
||||||
|
|
||||||
|
factory PurchasingAnalytic.empty() => PurchasingAnalytic(
|
||||||
|
organizationId: '',
|
||||||
|
outletId: '',
|
||||||
|
outletName: '',
|
||||||
|
dateFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
dateTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
groupBy: '',
|
||||||
|
summary: PurchasingAnalyticSummary.empty(),
|
||||||
|
data: [],
|
||||||
|
ingredientData: [],
|
||||||
|
vendorData: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticSummary with _$PurchasingAnalyticSummary {
|
||||||
|
const factory PurchasingAnalyticSummary({
|
||||||
|
required int totalPurchases,
|
||||||
|
required int totalPurchaseOrders,
|
||||||
|
required int totalQuantity,
|
||||||
|
required double averagePurchaseOrderValue,
|
||||||
|
required int totalIngredients,
|
||||||
|
required int totalVendors,
|
||||||
|
}) = _PurchasingAnalyticSummary;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticSummary.empty() =>
|
||||||
|
const PurchasingAnalyticSummary(
|
||||||
|
totalPurchases: 0,
|
||||||
|
totalPurchaseOrders: 0,
|
||||||
|
totalQuantity: 0,
|
||||||
|
averagePurchaseOrderValue: 0,
|
||||||
|
totalIngredients: 0,
|
||||||
|
totalVendors: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticData with _$PurchasingAnalyticData {
|
||||||
|
const factory PurchasingAnalyticData({
|
||||||
|
required DateTime date,
|
||||||
|
required int purchases,
|
||||||
|
required int purchaseOrders,
|
||||||
|
required int quantity,
|
||||||
|
required int ingredients,
|
||||||
|
required int vendors,
|
||||||
|
}) = _PurchasingAnalyticData;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticData.empty() => PurchasingAnalyticData(
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
purchases: 0,
|
||||||
|
purchaseOrders: 0,
|
||||||
|
quantity: 0,
|
||||||
|
ingredients: 0,
|
||||||
|
vendors: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingIngredientData with _$PurchasingIngredientData {
|
||||||
|
const factory PurchasingIngredientData({
|
||||||
|
required String ingredientId,
|
||||||
|
required String ingredientName,
|
||||||
|
required int quantity,
|
||||||
|
required int totalCost,
|
||||||
|
required double averageUnitCost,
|
||||||
|
required int purchaseOrderCount,
|
||||||
|
}) = _PurchasingIngredientData;
|
||||||
|
|
||||||
|
factory PurchasingIngredientData.empty() =>
|
||||||
|
const PurchasingIngredientData(
|
||||||
|
ingredientId: '',
|
||||||
|
ingredientName: '',
|
||||||
|
quantity: 0,
|
||||||
|
totalCost: 0,
|
||||||
|
averageUnitCost: 0,
|
||||||
|
purchaseOrderCount: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingVendorData with _$PurchasingVendorData {
|
||||||
|
const factory PurchasingVendorData({
|
||||||
|
required String vendorId,
|
||||||
|
required String vendorName,
|
||||||
|
required int totalCost,
|
||||||
|
required int purchaseOrderCount,
|
||||||
|
required int ingredientCount,
|
||||||
|
required int quantity,
|
||||||
|
}) = _PurchasingVendorData;
|
||||||
|
|
||||||
|
factory PurchasingVendorData.empty() => const PurchasingVendorData(
|
||||||
|
vendorId: '',
|
||||||
|
vendorName: '',
|
||||||
|
totalCost: 0,
|
||||||
|
purchaseOrderCount: 0,
|
||||||
|
ingredientCount: 0,
|
||||||
|
quantity: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,4 +44,10 @@ abstract class IAnalyticRepository {
|
|||||||
required DateTime dateTo,
|
required DateTime dateTo,
|
||||||
String? outletId,
|
String? outletId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<Either<AnalyticFailure, PurchasingAnalytic>> getPurchasing({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ part 'dto/inventory_analytic_dto.dart';
|
|||||||
part 'dto/dashboard_analytic_dto.dart';
|
part 'dto/dashboard_analytic_dto.dart';
|
||||||
part 'dto/product_analytic_dto.dart';
|
part 'dto/product_analytic_dto.dart';
|
||||||
part 'dto/payment_method_analytic_dto.dart';
|
part 'dto/payment_method_analytic_dto.dart';
|
||||||
|
part 'dto/purchasing_analytic_dto.dart';
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -637,3 +637,139 @@ Map<String, dynamic> _$$PaymentMethodSummaryDtoImplToJson(
|
|||||||
'total_payments': instance.totalPayments,
|
'total_payments': instance.totalPayments,
|
||||||
'average_order_value': instance.averageOrderValue,
|
'average_order_value': instance.averageOrderValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$PurchasingAnalyticDtoImpl _$$PurchasingAnalyticDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$PurchasingAnalyticDtoImpl(
|
||||||
|
organizationId: json['organization_id'] as String?,
|
||||||
|
outletId: json['outlet_id'] as String?,
|
||||||
|
outletName: json['outlet_name'] as String?,
|
||||||
|
dateFrom: json['date_from'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['date_from'] as String),
|
||||||
|
dateTo: json['date_to'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['date_to'] as String),
|
||||||
|
groupBy: json['group_by'] as String?,
|
||||||
|
summary: json['summary'] == null
|
||||||
|
? null
|
||||||
|
: PurchasingAnalyticSummaryDto.fromJson(
|
||||||
|
json['summary'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
data: (json['data'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => PurchasingAnalyticDataDto.fromJson(e as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
ingredientData: (json['ingredient_data'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(e) => PurchasingIngredientDataDto.fromJson(e as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
vendorData: (json['vendor_data'] as List<dynamic>?)
|
||||||
|
?.map((e) => PurchasingVendorDataDto.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PurchasingAnalyticDtoImplToJson(
|
||||||
|
_$PurchasingAnalyticDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'organization_id': instance.organizationId,
|
||||||
|
'outlet_id': instance.outletId,
|
||||||
|
'outlet_name': instance.outletName,
|
||||||
|
'date_from': instance.dateFrom?.toIso8601String(),
|
||||||
|
'date_to': instance.dateTo?.toIso8601String(),
|
||||||
|
'group_by': instance.groupBy,
|
||||||
|
'summary': instance.summary,
|
||||||
|
'data': instance.data,
|
||||||
|
'ingredient_data': instance.ingredientData,
|
||||||
|
'vendor_data': instance.vendorData,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$PurchasingAnalyticSummaryDtoImpl _$$PurchasingAnalyticSummaryDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$PurchasingAnalyticSummaryDtoImpl(
|
||||||
|
totalPurchases: json['total_purchases'] as num?,
|
||||||
|
totalPurchaseOrders: json['total_purchase_orders'] as num?,
|
||||||
|
totalQuantity: json['total_quantity'] as num?,
|
||||||
|
averagePurchaseOrderValue: json['average_purchase_order_value'] as num?,
|
||||||
|
totalIngredients: json['total_ingredients'] as num?,
|
||||||
|
totalVendors: json['total_vendors'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PurchasingAnalyticSummaryDtoImplToJson(
|
||||||
|
_$PurchasingAnalyticSummaryDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'total_purchases': instance.totalPurchases,
|
||||||
|
'total_purchase_orders': instance.totalPurchaseOrders,
|
||||||
|
'total_quantity': instance.totalQuantity,
|
||||||
|
'average_purchase_order_value': instance.averagePurchaseOrderValue,
|
||||||
|
'total_ingredients': instance.totalIngredients,
|
||||||
|
'total_vendors': instance.totalVendors,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$PurchasingAnalyticDataDtoImpl _$$PurchasingAnalyticDataDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$PurchasingAnalyticDataDtoImpl(
|
||||||
|
date: json['date'] == null ? null : DateTime.parse(json['date'] as String),
|
||||||
|
purchases: json['purchases'] as num?,
|
||||||
|
purchaseOrders: json['purchase_orders'] as num?,
|
||||||
|
quantity: json['quantity'] as num?,
|
||||||
|
ingredients: json['ingredients'] as num?,
|
||||||
|
vendors: json['vendors'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PurchasingAnalyticDataDtoImplToJson(
|
||||||
|
_$PurchasingAnalyticDataDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'date': instance.date?.toIso8601String(),
|
||||||
|
'purchases': instance.purchases,
|
||||||
|
'purchase_orders': instance.purchaseOrders,
|
||||||
|
'quantity': instance.quantity,
|
||||||
|
'ingredients': instance.ingredients,
|
||||||
|
'vendors': instance.vendors,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$PurchasingIngredientDataDtoImpl _$$PurchasingIngredientDataDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$PurchasingIngredientDataDtoImpl(
|
||||||
|
ingredientId: json['ingredient_id'] as String?,
|
||||||
|
ingredientName: json['ingredient_name'] as String?,
|
||||||
|
quantity: json['quantity'] as num?,
|
||||||
|
totalCost: json['total_cost'] as num?,
|
||||||
|
averageUnitCost: json['average_unit_cost'] as num?,
|
||||||
|
purchaseOrderCount: json['purchase_order_count'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PurchasingIngredientDataDtoImplToJson(
|
||||||
|
_$PurchasingIngredientDataDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'ingredient_id': instance.ingredientId,
|
||||||
|
'ingredient_name': instance.ingredientName,
|
||||||
|
'quantity': instance.quantity,
|
||||||
|
'total_cost': instance.totalCost,
|
||||||
|
'average_unit_cost': instance.averageUnitCost,
|
||||||
|
'purchase_order_count': instance.purchaseOrderCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$PurchasingVendorDataDtoImpl _$$PurchasingVendorDataDtoImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$PurchasingVendorDataDtoImpl(
|
||||||
|
vendorId: json['vendor_id'] as String?,
|
||||||
|
vendorName: json['vendor_name'] as String?,
|
||||||
|
totalCost: json['total_cost'] as num?,
|
||||||
|
purchaseOrderCount: json['purchase_order_count'] as num?,
|
||||||
|
ingredientCount: json['ingredient_count'] as num?,
|
||||||
|
quantity: json['quantity'] as num?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PurchasingVendorDataDtoImplToJson(
|
||||||
|
_$PurchasingVendorDataDtoImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'vendor_id': instance.vendorId,
|
||||||
|
'vendor_name': instance.vendorName,
|
||||||
|
'total_cost': instance.totalCost,
|
||||||
|
'purchase_order_count': instance.purchaseOrderCount,
|
||||||
|
'ingredient_count': instance.ingredientCount,
|
||||||
|
'quantity': instance.quantity,
|
||||||
|
};
|
||||||
|
|||||||
@@ -231,4 +231,35 @@ class AnalyticRemoteDataProvider {
|
|||||||
return DC.error(AnalyticFailure.serverError(e));
|
return DC.error(AnalyticFailure.serverError(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<DC<AnalyticFailure, PurchasingAnalyticDto>> fetchPurchasing({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> params = {
|
||||||
|
'date_from': dateFrom.toServerDate,
|
||||||
|
'date_to': dateTo.toServerDate,
|
||||||
|
};
|
||||||
|
if (outletId != null) params['outlet_id'] = outletId;
|
||||||
|
|
||||||
|
final response = await _apiClient.get(
|
||||||
|
ApiPath.purchasingAnalytic,
|
||||||
|
params: params,
|
||||||
|
headers: getAuthorizationHeader(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data['data'] == null) {
|
||||||
|
return DC.error(AnalyticFailure.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
final dto = PurchasingAnalyticDto.fromJson(response.data['data']);
|
||||||
|
|
||||||
|
return DC.data(dto);
|
||||||
|
} on ApiFailure catch (e, s) {
|
||||||
|
log('fetchPurchasingError', name: _logName, error: e, stackTrace: s);
|
||||||
|
return DC.error(AnalyticFailure.serverError(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
part of '../analytic_dtos.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticDto with _$PurchasingAnalyticDto {
|
||||||
|
const PurchasingAnalyticDto._();
|
||||||
|
|
||||||
|
const factory PurchasingAnalyticDto({
|
||||||
|
@JsonKey(name: 'organization_id') String? organizationId,
|
||||||
|
@JsonKey(name: 'outlet_id') String? outletId,
|
||||||
|
@JsonKey(name: 'outlet_name') String? outletName,
|
||||||
|
@JsonKey(name: 'date_from') DateTime? dateFrom,
|
||||||
|
@JsonKey(name: 'date_to') DateTime? dateTo,
|
||||||
|
@JsonKey(name: 'group_by') String? groupBy,
|
||||||
|
@JsonKey(name: 'summary') PurchasingAnalyticSummaryDto? summary,
|
||||||
|
@JsonKey(name: 'data') List<PurchasingAnalyticDataDto>? data,
|
||||||
|
@JsonKey(name: 'ingredient_data')
|
||||||
|
List<PurchasingIngredientDataDto>? ingredientData,
|
||||||
|
@JsonKey(name: 'vendor_data') List<PurchasingVendorDataDto>? vendorData,
|
||||||
|
}) = _PurchasingAnalyticDto;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PurchasingAnalyticDtoFromJson(json);
|
||||||
|
|
||||||
|
PurchasingAnalytic toDomain() => PurchasingAnalytic(
|
||||||
|
organizationId: organizationId ?? '',
|
||||||
|
outletId: outletId ?? '',
|
||||||
|
outletName: outletName ?? '',
|
||||||
|
dateFrom: dateFrom ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
dateTo: dateTo ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
groupBy: groupBy ?? '',
|
||||||
|
summary: summary?.toDomain() ?? PurchasingAnalyticSummary.empty(),
|
||||||
|
data: data?.map((e) => e.toDomain()).toList() ?? [],
|
||||||
|
ingredientData:
|
||||||
|
ingredientData?.map((e) => e.toDomain()).toList() ?? [],
|
||||||
|
vendorData: vendorData?.map((e) => e.toDomain()).toList() ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticSummaryDto with _$PurchasingAnalyticSummaryDto {
|
||||||
|
const PurchasingAnalyticSummaryDto._();
|
||||||
|
|
||||||
|
const factory PurchasingAnalyticSummaryDto({
|
||||||
|
@JsonKey(name: 'total_purchases') num? totalPurchases,
|
||||||
|
@JsonKey(name: 'total_purchase_orders') num? totalPurchaseOrders,
|
||||||
|
@JsonKey(name: 'total_quantity') num? totalQuantity,
|
||||||
|
@JsonKey(name: 'average_purchase_order_value')
|
||||||
|
num? averagePurchaseOrderValue,
|
||||||
|
@JsonKey(name: 'total_ingredients') num? totalIngredients,
|
||||||
|
@JsonKey(name: 'total_vendors') num? totalVendors,
|
||||||
|
}) = _PurchasingAnalyticSummaryDto;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticSummaryDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PurchasingAnalyticSummaryDtoFromJson(json);
|
||||||
|
|
||||||
|
PurchasingAnalyticSummary toDomain() => PurchasingAnalyticSummary(
|
||||||
|
totalPurchases: totalPurchases?.toInt() ?? 0,
|
||||||
|
totalPurchaseOrders: totalPurchaseOrders?.toInt() ?? 0,
|
||||||
|
totalQuantity: totalQuantity?.toInt() ?? 0,
|
||||||
|
averagePurchaseOrderValue:
|
||||||
|
averagePurchaseOrderValue?.toDouble() ?? 0,
|
||||||
|
totalIngredients: totalIngredients?.toInt() ?? 0,
|
||||||
|
totalVendors: totalVendors?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingAnalyticDataDto with _$PurchasingAnalyticDataDto {
|
||||||
|
const PurchasingAnalyticDataDto._();
|
||||||
|
|
||||||
|
const factory PurchasingAnalyticDataDto({
|
||||||
|
@JsonKey(name: 'date') DateTime? date,
|
||||||
|
@JsonKey(name: 'purchases') num? purchases,
|
||||||
|
@JsonKey(name: 'purchase_orders') num? purchaseOrders,
|
||||||
|
@JsonKey(name: 'quantity') num? quantity,
|
||||||
|
@JsonKey(name: 'ingredients') num? ingredients,
|
||||||
|
@JsonKey(name: 'vendors') num? vendors,
|
||||||
|
}) = _PurchasingAnalyticDataDto;
|
||||||
|
|
||||||
|
factory PurchasingAnalyticDataDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PurchasingAnalyticDataDtoFromJson(json);
|
||||||
|
|
||||||
|
PurchasingAnalyticData toDomain() => PurchasingAnalyticData(
|
||||||
|
date: date ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
purchases: purchases?.toInt() ?? 0,
|
||||||
|
purchaseOrders: purchaseOrders?.toInt() ?? 0,
|
||||||
|
quantity: quantity?.toInt() ?? 0,
|
||||||
|
ingredients: ingredients?.toInt() ?? 0,
|
||||||
|
vendors: vendors?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingIngredientDataDto with _$PurchasingIngredientDataDto {
|
||||||
|
const PurchasingIngredientDataDto._();
|
||||||
|
|
||||||
|
const factory PurchasingIngredientDataDto({
|
||||||
|
@JsonKey(name: 'ingredient_id') String? ingredientId,
|
||||||
|
@JsonKey(name: 'ingredient_name') String? ingredientName,
|
||||||
|
@JsonKey(name: 'quantity') num? quantity,
|
||||||
|
@JsonKey(name: 'total_cost') num? totalCost,
|
||||||
|
@JsonKey(name: 'average_unit_cost') num? averageUnitCost,
|
||||||
|
@JsonKey(name: 'purchase_order_count') num? purchaseOrderCount,
|
||||||
|
}) = _PurchasingIngredientDataDto;
|
||||||
|
|
||||||
|
factory PurchasingIngredientDataDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PurchasingIngredientDataDtoFromJson(json);
|
||||||
|
|
||||||
|
PurchasingIngredientData toDomain() => PurchasingIngredientData(
|
||||||
|
ingredientId: ingredientId ?? '',
|
||||||
|
ingredientName: ingredientName ?? '',
|
||||||
|
quantity: quantity?.toInt() ?? 0,
|
||||||
|
totalCost: totalCost?.toInt() ?? 0,
|
||||||
|
averageUnitCost: averageUnitCost?.toDouble() ?? 0,
|
||||||
|
purchaseOrderCount: purchaseOrderCount?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PurchasingVendorDataDto with _$PurchasingVendorDataDto {
|
||||||
|
const PurchasingVendorDataDto._();
|
||||||
|
|
||||||
|
const factory PurchasingVendorDataDto({
|
||||||
|
@JsonKey(name: 'vendor_id') String? vendorId,
|
||||||
|
@JsonKey(name: 'vendor_name') String? vendorName,
|
||||||
|
@JsonKey(name: 'total_cost') num? totalCost,
|
||||||
|
@JsonKey(name: 'purchase_order_count') num? purchaseOrderCount,
|
||||||
|
@JsonKey(name: 'ingredient_count') num? ingredientCount,
|
||||||
|
@JsonKey(name: 'quantity') num? quantity,
|
||||||
|
}) = _PurchasingVendorDataDto;
|
||||||
|
|
||||||
|
factory PurchasingVendorDataDto.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PurchasingVendorDataDtoFromJson(json);
|
||||||
|
|
||||||
|
PurchasingVendorData toDomain() => PurchasingVendorData(
|
||||||
|
vendorId: vendorId ?? '',
|
||||||
|
vendorName: vendorName ?? '',
|
||||||
|
totalCost: totalCost?.toInt() ?? 0,
|
||||||
|
purchaseOrderCount: purchaseOrderCount?.toInt() ?? 0,
|
||||||
|
ingredientCount: ingredientCount?.toInt() ?? 0,
|
||||||
|
quantity: quantity?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -176,4 +176,25 @@ class AnalyticRepository implements IAnalyticRepository {
|
|||||||
return left(const AnalyticFailure.unexpectedError());
|
return left(const AnalyticFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<AnalyticFailure, PurchasingAnalytic>> getPurchasing({
|
||||||
|
required DateTime dateFrom,
|
||||||
|
required DateTime dateTo,
|
||||||
|
String? outletId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final result = await _dataProvider.fetchPurchasing(
|
||||||
|
dateFrom: dateFrom,
|
||||||
|
dateTo: dateTo,
|
||||||
|
outletId: _resolveOutletId(outletId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.hasError) return left(result.error!);
|
||||||
|
return right(result.data!.toDomain());
|
||||||
|
} catch (e, s) {
|
||||||
|
log('getPurchasingError', name: _logName, error: e, stackTrace: s);
|
||||||
|
return left(const AnalyticFailure.unexpectedError());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import 'package:apskel_owner_flutter/application/analytic/product_analytic_loade
|
|||||||
as _i221;
|
as _i221;
|
||||||
import 'package:apskel_owner_flutter/application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart'
|
import 'package:apskel_owner_flutter/application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart'
|
||||||
as _i11;
|
as _i11;
|
||||||
|
import 'package:apskel_owner_flutter/application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart'
|
||||||
|
as _i755;
|
||||||
import 'package:apskel_owner_flutter/application/analytic/sales_loader/sales_loader_bloc.dart'
|
import 'package:apskel_owner_flutter/application/analytic/sales_loader/sales_loader_bloc.dart'
|
||||||
as _i889;
|
as _i889;
|
||||||
import 'package:apskel_owner_flutter/application/auth/auth_bloc.dart' as _i945;
|
import 'package:apskel_owner_flutter/application/auth/auth_bloc.dart' as _i945;
|
||||||
@@ -248,6 +250,9 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i850.OutletLocalDataProvider>(),
|
gh<_i850.OutletLocalDataProvider>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i755.PurchasingAnalyticLoaderBloc>(
|
||||||
|
() => _i755.PurchasingAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||||
|
);
|
||||||
gh.factory<_i889.SalesLoaderBloc>(
|
gh.factory<_i889.SalesLoaderBloc>(
|
||||||
() => _i889.SalesLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
() => _i889.SalesLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -25,12 +26,23 @@ void main() async {
|
|||||||
kReleaseMode ? Environment.prod : Environment.dev,
|
kReleaseMode ? Environment.prod : Environment.dev,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Setup Crashlytics
|
||||||
|
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
|
||||||
|
PlatformDispatcher.instance.onError = (error, stack) {
|
||||||
|
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize FCM after dependencies are ready
|
// Initialize FCM after dependencies are ready
|
||||||
|
try {
|
||||||
await getIt<FcmService>().initialize(
|
await getIt<FcmService>().initialize(
|
||||||
onMessageTap: (message) {
|
onMessageTap: (message) {
|
||||||
debugPrint('[FCM] Navigate based on: ${message.data}');
|
debugPrint('[FCM] Navigate based on: ${message.data}');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[FCM] Initialization failed: $e');
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const AppWidget());
|
runApp(const AppWidget());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ class HomeFeature extends StatelessWidget {
|
|||||||
iconPath: Assets.icons.icReportProduct.path,
|
iconPath: Assets.icons.icReportProduct.path,
|
||||||
onTap: () => context.router.push(ProductAnalyticRoute()),
|
onTap: () => context.router.push(ProductAnalyticRoute()),
|
||||||
),
|
),
|
||||||
|
// HomeFeatureTile(
|
||||||
|
// title: context.lang.inventory,
|
||||||
|
// iconPath: Assets.icons.icReportProduct.path,
|
||||||
|
// onTap: () => context.router.push(InventoryRoute()),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Row(
|
// Row(
|
||||||
|
|||||||
@@ -1,51 +1,70 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
import '../../../application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart';
|
||||||
|
import '../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart';
|
||||||
import '../../../common/extension/extension.dart';
|
import '../../../common/extension/extension.dart';
|
||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
|
import '../../../injection.dart';
|
||||||
import '../../components/appbar/appbar.dart';
|
import '../../components/appbar/appbar.dart';
|
||||||
import 'widgets/purchase_tile.dart';
|
import '../../components/field/date_range_picker_field.dart';
|
||||||
|
import '../../components/spacer/spacer.dart';
|
||||||
|
import 'widgets/ingredient_card.dart';
|
||||||
|
import 'widgets/outlet_selector_field.dart';
|
||||||
|
import 'widgets/purchase_daily_tile.dart';
|
||||||
import 'widgets/stat_card.dart';
|
import 'widgets/stat_card.dart';
|
||||||
import 'widgets/status_chip.dart';
|
import 'widgets/vendor_card.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PurchasePage extends StatefulWidget {
|
class PurchasePage extends StatefulWidget implements AutoRouteWrapper {
|
||||||
const PurchasePage({super.key});
|
const PurchasePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PurchasePage> createState() => _PurchasePageState();
|
State<PurchasePage> createState() => _PurchasePageState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => getIt<PurchasingAnalyticLoaderBloc>()
|
||||||
|
..add(const PurchasingAnalyticLoaderEvent.fetched()),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => getIt<OutletListLoaderBloc>()
|
||||||
|
..add(const OutletListLoaderEvent.fetched()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: this,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PurchasePageState extends State<PurchasePage>
|
class _PurchasePageState extends State<PurchasePage>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late AnimationController cardAnimation;
|
late AnimationController _fadeController;
|
||||||
String selectedFilter = 'Semua';
|
late Animation<double> _fadeAnimation;
|
||||||
final List<String> filterOptions = [
|
|
||||||
'Semua',
|
|
||||||
'Pending',
|
|
||||||
'Completed',
|
|
||||||
'Cancelled',
|
|
||||||
];
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> purchaseData = [
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_fadeController = AnimationController(
|
||||||
cardAnimation = AnimationController(
|
duration: const Duration(milliseconds: 600),
|
||||||
duration: const Duration(milliseconds: 1200),
|
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
_fadeAnimation = CurvedAnimation(
|
||||||
cardAnimation.forward();
|
parent: _fadeController,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
if (mounted) _fadeController.forward();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cardAnimation.dispose();
|
_fadeController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,142 +72,522 @@ class _PurchasePageState extends State<PurchasePage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColor.background,
|
backgroundColor: AppColor.background,
|
||||||
body: CustomScrollView(
|
body: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
// Re-fetch when date range changes
|
||||||
|
BlocListener<PurchasingAnalyticLoaderBloc,
|
||||||
|
PurchasingAnalyticLoaderState>(
|
||||||
|
listenWhen: (prev, curr) =>
|
||||||
|
prev.dateFrom != curr.dateFrom ||
|
||||||
|
prev.dateTo != curr.dateTo,
|
||||||
|
listener: (context, _) => context
|
||||||
|
.read<PurchasingAnalyticLoaderBloc>()
|
||||||
|
.add(const PurchasingAnalyticLoaderEvent.fetched()),
|
||||||
|
),
|
||||||
|
// Re-fetch when outlet changes
|
||||||
|
BlocListener<PurchasingAnalyticLoaderBloc,
|
||||||
|
PurchasingAnalyticLoaderState>(
|
||||||
|
listenWhen: (prev, curr) => prev.outletId != curr.outletId,
|
||||||
|
listener: (context, _) => context
|
||||||
|
.read<PurchasingAnalyticLoaderBloc>()
|
||||||
|
.add(const PurchasingAnalyticLoaderEvent.fetched()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: BlocBuilder<OutletListLoaderBloc, OutletListLoaderState>(
|
||||||
|
builder: (context, outletListState) {
|
||||||
|
return BlocBuilder<PurchasingAnalyticLoaderBloc,
|
||||||
|
PurchasingAnalyticLoaderState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
|
// App Bar
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
expandedHeight: 120.0,
|
expandedHeight: 120.0,
|
||||||
floating: false,
|
floating: false,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: AppColor.primary,
|
backgroundColor: AppColor.primary,
|
||||||
|
flexibleSpace:
|
||||||
flexibleSpace: CustomAppBar(title: context.lang.purchase),
|
CustomAppBar(title: context.lang.purchase),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Stats Cards
|
// Date Range + Outlet Picker
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: FadeTransition(
|
||||||
color: AppColor.background,
|
opacity: _fadeAnimation,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
DateRangePickerField(
|
||||||
child: PurchaseStatCard(
|
maxDate: DateTime.now(),
|
||||||
title: context.lang.total_purchase,
|
startDate: state.dateFrom,
|
||||||
value: 'Rp 0',
|
endDate: state.dateTo,
|
||||||
icon: LineIcons.shoppingCart,
|
onChanged: (startDate, endDate) {
|
||||||
iconColor: AppColor.success,
|
context
|
||||||
cardAnimation: cardAnimation,
|
.read<PurchasingAnalyticLoaderBloc>()
|
||||||
|
.add(
|
||||||
|
PurchasingAnalyticLoaderEvent
|
||||||
|
.rangeDateChanged(
|
||||||
|
startDate!,
|
||||||
|
endDate!,
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SpaceHeight(8),
|
||||||
Expanded(
|
PurchaseOutletSelectorField(
|
||||||
child: PurchaseStatCard(
|
selectedOutletId: state.outletId,
|
||||||
title: context.lang.pending_order,
|
outlets: outletListState.outlets,
|
||||||
value: '0 ${context.lang.orders}',
|
isLoading: outletListState.isFetching,
|
||||||
icon: LineIcons.clock,
|
onOutletChanged: (outletId) {
|
||||||
iconColor: AppColor.warning,
|
context
|
||||||
cardAnimation: cardAnimation,
|
.read<PurchasingAnalyticLoaderBloc>()
|
||||||
),
|
.add(
|
||||||
|
PurchasingAnalyticLoaderEvent
|
||||||
|
.outletChanged(outletId),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Filter Section
|
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||||
|
|
||||||
|
// Summary Section
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: FadeTransition(
|
||||||
color: AppColor.surface,
|
opacity: _fadeAnimation,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.summary,
|
||||||
|
style: AppStyle.xxl.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SpaceHeight(16),
|
||||||
|
state.isFetching
|
||||||
|
? _buildSummaryShimmer()
|
||||||
|
: _buildSummaryCards(state),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(24)),
|
||||||
|
|
||||||
|
// Total Purchases Highlight Card
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: state.isFetching
|
||||||
|
? _buildHighlightShimmer()
|
||||||
|
: _buildTotalPurchasesCard(state),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(24)),
|
||||||
|
|
||||||
|
// Daily Breakdown Header
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
context.lang.daily_breakdown,
|
||||||
|
style: AppStyle.xxl.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(12)),
|
||||||
|
|
||||||
|
state.isFetching
|
||||||
|
? _buildListShimmer()
|
||||||
|
: _buildDailyList(state),
|
||||||
|
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(24)),
|
||||||
|
|
||||||
|
// Ingredient Header
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
'Bahan Baku',
|
||||||
|
style: AppStyle.xxl.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(12)),
|
||||||
|
|
||||||
|
state.isFetching
|
||||||
|
? _buildListShimmer()
|
||||||
|
: _buildIngredientList(state),
|
||||||
|
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(24)),
|
||||||
|
|
||||||
|
// Vendor Header
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text(
|
||||||
|
'Vendor',
|
||||||
|
style: AppStyle.xxl.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(12)),
|
||||||
|
|
||||||
|
state.isFetching
|
||||||
|
? _buildListShimmer()
|
||||||
|
: _buildVendorList(state),
|
||||||
|
|
||||||
|
const SliverToBoxAdapter(child: SpaceHeight(80)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Summary Cards ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Widget _buildSummaryCards(PurchasingAnalyticLoaderState state) {
|
||||||
|
final s = state.purchasing.summary;
|
||||||
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Total Pembelian',
|
||||||
|
value: s.totalPurchases.currencyFormatRp,
|
||||||
|
icon: LineIcons.shoppingCart,
|
||||||
|
iconColor: AppColor.primary,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Total PO',
|
||||||
|
value: '${s.totalPurchaseOrders} PO',
|
||||||
|
icon: LineIcons.fileAlt,
|
||||||
|
iconColor: AppColor.info,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SpaceHeight(12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Total Qty',
|
||||||
|
value: '${s.totalQuantity} pcs',
|
||||||
|
icon: LineIcons.boxes,
|
||||||
|
iconColor: AppColor.warning,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Rata-rata PO',
|
||||||
|
value: s.averagePurchaseOrderValue.round().currencyFormatRp,
|
||||||
|
icon: LineIcons.dollarSign,
|
||||||
|
iconColor: AppColor.secondary,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SpaceHeight(12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Bahan Baku',
|
||||||
|
value: '${s.totalIngredients} item',
|
||||||
|
icon: LineIcons.leaf,
|
||||||
|
iconColor: AppColor.secondaryDark,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(
|
||||||
|
child: PurchaseStatCard(
|
||||||
|
title: 'Vendor',
|
||||||
|
value: '${s.totalVendors} vendor',
|
||||||
|
icon: LineIcons.truck,
|
||||||
|
iconColor: AppColor.primaryDark,
|
||||||
|
cardAnimation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTotalPurchasesCard(PurchasingAnalyticLoaderState state) {
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
duration: const Duration(milliseconds: 900),
|
||||||
|
curve: Curves.bounceOut,
|
||||||
|
builder: (context, value, _) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: value,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: AppColor.primaryGradient,
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColor.primary.withOpacity(0.35),
|
||||||
|
blurRadius: 16,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
LineIcons.shoppingBag,
|
||||||
|
color: AppColor.textWhite,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SpaceWidth(16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
context.lang.history_purchase,
|
context.lang.total_purchase,
|
||||||
style: AppStyle.lg.copyWith(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
color: AppColor.textWhite.withOpacity(0.9),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
const SpaceHeight(4),
|
||||||
padding: const EdgeInsets.symmetric(
|
Text(
|
||||||
horizontal: 12,
|
state.purchasing.summary.totalPurchases
|
||||||
vertical: 6,
|
.currencyFormatRp,
|
||||||
),
|
style: const TextStyle(
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.primary,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${purchaseData.length} ${context.lang.orders}',
|
|
||||||
style: AppStyle.sm.copyWith(
|
|
||||||
color: AppColor.textWhite,
|
color: AppColor.textWhite,
|
||||||
),
|
fontSize: 24,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: ListView.builder(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: filterOptions.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final isSelected =
|
|
||||||
selectedFilter == filterOptions[index];
|
|
||||||
return PurchaseStatusChip(
|
|
||||||
isSelected: isSelected,
|
|
||||||
text: filterOptions[index],
|
|
||||||
onSelected: (selected) {
|
|
||||||
setState(() {
|
|
||||||
selectedFilter = filterOptions[index];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${state.purchasing.summary.totalPurchaseOrders} PO',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColor.textWhite,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'purchase order',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColor.textWhite.withOpacity(0.8),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Purchase List
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
sliver: SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
final purchase = purchaseData[index];
|
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: cardAnimation,
|
|
||||||
builder: (context, child) {
|
|
||||||
final delay = index * 0.1;
|
|
||||||
final animValue = (cardAnimation.value - delay).clamp(
|
|
||||||
0.0,
|
|
||||||
1.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Transform.translate(
|
|
||||||
offset: Offset(0, 30 * (1 - animValue)),
|
|
||||||
child: Opacity(
|
|
||||||
opacity: animValue,
|
|
||||||
child: PurchaseTile(purchase: purchase, index: index),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, childCount: purchaseData.length),
|
}
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Bottom spacing for FAB
|
// ─── Lists ────────────────────────────────────────────────────────────────
|
||||||
const SliverToBoxAdapter(child: SizedBox(height: 80)),
|
|
||||||
|
Widget _buildDailyList(PurchasingAnalyticLoaderState state) {
|
||||||
|
if (state.purchasing.data.isEmpty) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: _buildEmptyState('Tidak ada data harian'));
|
||||||
|
}
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) => PurchaseDailyTile(
|
||||||
|
data: state.purchasing.data[index],
|
||||||
|
index: index,
|
||||||
|
animation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
childCount: state.purchasing.data.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIngredientList(PurchasingAnalyticLoaderState state) {
|
||||||
|
if (state.purchasing.ingredientData.isEmpty) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: _buildEmptyState('Tidak ada data bahan baku'));
|
||||||
|
}
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) => PurchaseIngredientCard(
|
||||||
|
data: state.purchasing.ingredientData[index],
|
||||||
|
index: index,
|
||||||
|
animation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
childCount: state.purchasing.ingredientData.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVendorList(PurchasingAnalyticLoaderState state) {
|
||||||
|
if (state.purchasing.vendorData.isEmpty) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: _buildEmptyState('Tidak ada data vendor'));
|
||||||
|
}
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) => PurchaseVendorCard(
|
||||||
|
data: state.purchasing.vendorData[index],
|
||||||
|
index: index,
|
||||||
|
animation: _fadeAnimation,
|
||||||
|
),
|
||||||
|
childCount: state.purchasing.vendorData.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Empty State ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Widget _buildEmptyState(String message) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.surface,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: AppColor.border.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Shimmer Loaders ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Widget _buildSummaryShimmer() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SpaceHeight(12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SpaceHeight(12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
|
const SpaceWidth(12),
|
||||||
|
Expanded(child: _shimmerCard(height: 100)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHighlightShimmer() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: _shimmerCard(height: 88),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListShimmer() {
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(_, __) => Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
child: _shimmerCard(height: 72),
|
||||||
|
),
|
||||||
|
childCount: 4,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _shimmerCard({required double height}) {
|
||||||
|
return Shimmer.fromColors(
|
||||||
|
baseColor: Colors.grey[300]!,
|
||||||
|
highlightColor: Colors.grey[100]!,
|
||||||
|
child: Container(
|
||||||
|
height: height,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
|
import '../../../../common/extension/extension.dart';
|
||||||
|
import '../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../domain/analytic/analytic.dart';
|
||||||
|
|
||||||
|
class PurchaseIngredientCard extends StatelessWidget {
|
||||||
|
final PurchasingIngredientData data;
|
||||||
|
final int index;
|
||||||
|
final Animation<double> animation;
|
||||||
|
|
||||||
|
const PurchaseIngredientCard({
|
||||||
|
super.key,
|
||||||
|
required this.data,
|
||||||
|
required this.index,
|
||||||
|
required this.animation,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.surface,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: AppColor.border.withOpacity(0.25)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.04),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Rank badge
|
||||||
|
Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: AppColor.primaryGradient,
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${index + 1}',
|
||||||
|
style: AppStyle.sm.copyWith(
|
||||||
|
color: AppColor.textWhite,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Ingredient icon
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.secondary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.leaf,
|
||||||
|
color: AppColor.secondary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Name & details
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
data.ingredientName,
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_Chip(
|
||||||
|
label: '${data.quantity} pcs',
|
||||||
|
color: AppColor.warning,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_Chip(
|
||||||
|
label: '${data.purchaseOrderCount} PO',
|
||||||
|
color: AppColor.info,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Cost column
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
data.totalCost.currencyFormatRp,
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: AppColor.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'@ ${data.averageUnitCost.round().currencyFormatRp}',
|
||||||
|
style: AppStyle.xs.copyWith(
|
||||||
|
color: AppColor.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Chip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const _Chip({required this.label, required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: AppStyle.xs.copyWith(
|
||||||
|
color: color,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../domain/outlet/outlet.dart';
|
||||||
|
|
||||||
|
/// Outlet selector field — styled like DateRangePickerField.
|
||||||
|
/// Opens a bottom sheet to pick an outlet or "Semua Outlet".
|
||||||
|
class PurchaseOutletSelectorField extends StatefulWidget {
|
||||||
|
final String? selectedOutletId;
|
||||||
|
final List<Outlet> outlets;
|
||||||
|
final bool isLoading;
|
||||||
|
final ValueChanged<String?> onOutletChanged;
|
||||||
|
|
||||||
|
const PurchaseOutletSelectorField({
|
||||||
|
super.key,
|
||||||
|
required this.selectedOutletId,
|
||||||
|
required this.outlets,
|
||||||
|
required this.isLoading,
|
||||||
|
required this.onOutletChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PurchaseOutletSelectorField> createState() =>
|
||||||
|
_PurchaseOutletSelectorFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PurchaseOutletSelectorFieldState
|
||||||
|
extends State<PurchaseOutletSelectorField> {
|
||||||
|
bool _isPressed = false;
|
||||||
|
|
||||||
|
Outlet? get _selectedOutlet => widget.outlets
|
||||||
|
.where((o) => o.id == widget.selectedOutletId)
|
||||||
|
.firstOrNull;
|
||||||
|
|
||||||
|
String get _label => _selectedOutlet?.name ?? 'Semua Outlet';
|
||||||
|
|
||||||
|
bool get _hasValue => widget.selectedOutletId != null;
|
||||||
|
|
||||||
|
void _showSheet() {
|
||||||
|
if (widget.isLoading) return;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
builder: (_) => _OutletBottomSheet(
|
||||||
|
outlets: widget.outlets,
|
||||||
|
selectedOutletId: widget.selectedOutletId,
|
||||||
|
onSelected: (outletId) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.onOutletChanged(outletId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _showSheet,
|
||||||
|
onTapDown: (_) => setState(() => _isPressed = true),
|
||||||
|
onTapUp: (_) => setState(() => _isPressed = false),
|
||||||
|
onTapCancel: () => setState(() => _isPressed = false),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
height: 52,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isPressed ? AppColor.backgroundLight : AppColor.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: _isPressed ? AppColor.primary : AppColor.border,
|
||||||
|
width: _isPressed ? 2 : 1,
|
||||||
|
),
|
||||||
|
boxShadow: _isPressed
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColor.primary.withOpacity(0.1),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight:
|
||||||
|
_hasValue ? FontWeight.w500 : FontWeight.w400,
|
||||||
|
color: _hasValue
|
||||||
|
? AppColor.textPrimary
|
||||||
|
: AppColor.textSecondary,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: widget.isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: AppColor.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.store_rounded,
|
||||||
|
size: 20,
|
||||||
|
color: AppColor.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Bottom Sheet ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class _OutletBottomSheet extends StatelessWidget {
|
||||||
|
final List<Outlet> outlets;
|
||||||
|
final String? selectedOutletId;
|
||||||
|
final ValueChanged<String?> onSelected;
|
||||||
|
|
||||||
|
const _OutletBottomSheet({
|
||||||
|
required this.outlets,
|
||||||
|
required this.selectedOutletId,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Handle bar
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Pilih Outlet',
|
||||||
|
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// "Semua Outlet" option
|
||||||
|
_OutletItem(
|
||||||
|
label: 'Semua Outlet',
|
||||||
|
icon: Icons.store_rounded,
|
||||||
|
isSelected: selectedOutletId == null,
|
||||||
|
onTap: () => onSelected(null),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
|
||||||
|
// Individual outlets
|
||||||
|
...outlets.map(
|
||||||
|
(outlet) => Column(
|
||||||
|
children: [
|
||||||
|
_OutletItem(
|
||||||
|
label: outlet.name,
|
||||||
|
icon: Icons.storefront_rounded,
|
||||||
|
isSelected: selectedOutletId == outlet.id,
|
||||||
|
isActive: outlet.isActive,
|
||||||
|
onTap: () => onSelected(outlet.id),
|
||||||
|
),
|
||||||
|
if (outlet != outlets.last) const Divider(height: 1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── List Item ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class _OutletItem extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final bool isSelected;
|
||||||
|
final bool? isActive;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _OutletItem({
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onTap,
|
||||||
|
this.isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: onTap,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
|
leading: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? AppColor.primary.withOpacity(0.1)
|
||||||
|
: AppColor.background,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
color: isSelected ? AppColor.primary : AppColor.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||||
|
color: isSelected ? AppColor.primary : AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (isActive != null) ...[
|
||||||
|
Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: isActive! ? AppColor.success : AppColor.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
if (isSelected)
|
||||||
|
const Icon(Icons.check_rounded, color: AppColor.primary, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
|
import '../../../../common/extension/extension.dart';
|
||||||
|
import '../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../domain/analytic/analytic.dart';
|
||||||
|
|
||||||
|
class PurchaseDailyTile extends StatelessWidget {
|
||||||
|
final PurchasingAnalyticData data;
|
||||||
|
final int index;
|
||||||
|
final Animation<double> animation;
|
||||||
|
|
||||||
|
const PurchaseDailyTile({
|
||||||
|
super.key,
|
||||||
|
required this.data,
|
||||||
|
required this.index,
|
||||||
|
required this.animation,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.surface,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: AppColor.border.withOpacity(0.25)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.04),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ExpansionTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
),
|
||||||
|
collapsedShape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
),
|
||||||
|
leading: Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.calendar,
|
||||||
|
color: AppColor.primary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
data.date.toDate,
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
data.purchases.currencyFormatRp,
|
||||||
|
style: AppStyle.sm.copyWith(
|
||||||
|
color: AppColor.primary,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.info.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${data.purchaseOrders} PO',
|
||||||
|
style: AppStyle.xs.copyWith(
|
||||||
|
color: AppColor.info,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _DetailItem(
|
||||||
|
icon: LineIcons.boxes,
|
||||||
|
label: 'Qty',
|
||||||
|
value: '${data.quantity} pcs',
|
||||||
|
color: AppColor.warning,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _DetailItem(
|
||||||
|
icon: LineIcons.leaf,
|
||||||
|
label: 'Bahan',
|
||||||
|
value: '${data.ingredients} item',
|
||||||
|
color: AppColor.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _DetailItem(
|
||||||
|
icon: LineIcons.truck,
|
||||||
|
label: 'Vendor',
|
||||||
|
value: '${data.vendors}',
|
||||||
|
color: AppColor.primaryDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailItem extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const _DetailItem({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 18),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: AppStyle.sm.copyWith(
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:line_icons/line_icons.dart';
|
|
||||||
|
|
||||||
import '../../../../common/extension/extension.dart';
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class PurchaseTile extends StatelessWidget {
|
|
||||||
final Map<String, dynamic> purchase;
|
|
||||||
final int index;
|
|
||||||
const PurchaseTile({super.key, required this.purchase, required this.index});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Color statusColor;
|
|
||||||
|
|
||||||
switch (purchase['status']) {
|
|
||||||
case 'Completed':
|
|
||||||
statusColor = AppColor.success;
|
|
||||||
break;
|
|
||||||
case 'Pending':
|
|
||||||
statusColor = AppColor.warning;
|
|
||||||
break;
|
|
||||||
case 'Cancelled':
|
|
||||||
statusColor = AppColor.error;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
statusColor = AppColor.textSecondary;
|
|
||||||
}
|
|
||||||
return AnimatedContainer(
|
|
||||||
duration: Duration(milliseconds: 300 + (index * 50)),
|
|
||||||
curve: Curves.easeOutCubic,
|
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: Material(
|
|
||||||
elevation: 0,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [AppColor.surface, AppColor.surface.withOpacity(0.95)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppColor.border.withOpacity(0.2),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.primary.withOpacity(0.08),
|
|
||||||
blurRadius: 25,
|
|
||||||
offset: const Offset(0, 10),
|
|
||||||
spreadRadius: 0,
|
|
||||||
),
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.black.withOpacity(0.04),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 6,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: AppColor.primaryGradient,
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.primary.withOpacity(0.3),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
purchase['id'],
|
|
||||||
style: AppStyle.sm.copyWith(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: AppColor.textWhite,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
statusColor.withOpacity(0.15),
|
|
||||||
statusColor.withOpacity(0.05),
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: statusColor.withOpacity(0.2),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: statusColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
purchase['status'],
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: statusColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.background.withOpacity(0.5),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppColor.border.withOpacity(0.3),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
LineIcons.building,
|
|
||||||
color: AppColor.primary,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
purchase['supplier'],
|
|
||||||
style: AppStyle.md.copyWith(
|
|
||||||
color: AppColor.textPrimary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.info.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
LineIcons.calendar,
|
|
||||||
color: AppColor.info,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
purchase['date'],
|
|
||||||
style: AppStyle.sm.copyWith(
|
|
||||||
color: AppColor.textSecondary,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColor.secondary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
LineIcons.shoppingBag,
|
|
||||||
color: AppColor.secondary,
|
|
||||||
size: 14,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'${purchase['items']} ${context.lang.items}',
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: AppColor.secondary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
context.lang.total_purchase,
|
|
||||||
style: AppStyle.xs.copyWith(
|
|
||||||
color: AppColor.textSecondary,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'Rp ${purchase['total'].toString().replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}',
|
|
||||||
style: AppStyle.lg.copyWith(
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
color: AppColor.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildActionButton(LineIcons.eye, AppColor.info, () {}),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_buildActionButton(
|
|
||||||
LineIcons.edit,
|
|
||||||
AppColor.warning,
|
|
||||||
() {},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_buildActionButton(
|
|
||||||
LineIcons.trash,
|
|
||||||
AppColor.error,
|
|
||||||
() {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildActionButton(
|
|
||||||
IconData icon,
|
|
||||||
Color color,
|
|
||||||
VoidCallback onPressed,
|
|
||||||
) {
|
|
||||||
return Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [color.withOpacity(0.15), color.withOpacity(0.05)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: color.withOpacity(0.2), width: 1),
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
onTap: onPressed,
|
|
||||||
child: Center(child: Icon(icon, color: color, size: 18)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ class PurchaseStatCard extends StatelessWidget {
|
|||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Color iconColor;
|
final Color iconColor;
|
||||||
final Animation<double> cardAnimation;
|
final Animation<double> cardAnimation;
|
||||||
|
|
||||||
const PurchaseStatCard({
|
const PurchaseStatCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -25,65 +26,41 @@ class PurchaseStatCard extends StatelessWidget {
|
|||||||
return Transform.scale(
|
return Transform.scale(
|
||||||
scale: 0.8 + (cardAnimation.value * 0.2),
|
scale: 0.8 + (cardAnimation.value * 0.2),
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: cardAnimation.value,
|
opacity: cardAnimation.value.clamp(0.0, 1.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: AppColor.surface,
|
||||||
colors: [AppColor.surface, AppColor.surface.withOpacity(0.9)],
|
borderRadius: BorderRadius.circular(16),
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColor.primary.withOpacity(0.1),
|
color: AppColor.primary.withOpacity(0.08),
|
||||||
blurRadius: 20,
|
blurRadius: 16,
|
||||||
offset: const Offset(0, 8),
|
offset: const Offset(0, 6),
|
||||||
spreadRadius: 0,
|
|
||||||
),
|
),
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColor.black.withOpacity(0.05),
|
color: AppColor.black.withOpacity(0.04),
|
||||||
blurRadius: 10,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: AppColor.border.withOpacity(0.3),
|
color: AppColor.border.withOpacity(0.25),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: iconColor.withOpacity(0.12),
|
||||||
colors: [
|
borderRadius: BorderRadius.circular(10),
|
||||||
iconColor.withOpacity(0.15),
|
|
||||||
iconColor.withOpacity(0.05),
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
child: Icon(icon, color: iconColor, size: 22),
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: iconColor.withOpacity(0.2),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 12),
|
||||||
),
|
|
||||||
child: Icon(icon, color: iconColor, size: 24),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: AppStyle.sm.copyWith(
|
style: AppStyle.sm.copyWith(
|
||||||
@@ -91,14 +68,16 @@ class PurchaseStatCard extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: AppStyle.lg.copyWith(
|
style: AppStyle.md.copyWith(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
color: AppColor.textPrimary,
|
color: AppColor.textPrimary,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
|
|
||||||
class PurchaseStatusChip extends StatelessWidget {
|
|
||||||
final bool isSelected;
|
|
||||||
final String text;
|
|
||||||
final Function(bool) onSelected;
|
|
||||||
const PurchaseStatusChip({
|
|
||||||
super.key,
|
|
||||||
required this.isSelected,
|
|
||||||
required this.text,
|
|
||||||
required this.onSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
|
||||||
child: FilterChip(
|
|
||||||
selected: isSelected,
|
|
||||||
label: Text(
|
|
||||||
text,
|
|
||||||
style: AppStyle.sm.copyWith(
|
|
||||||
color: isSelected ? AppColor.textWhite : AppColor.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: AppColor.backgroundLight,
|
|
||||||
selectedColor: AppColor.primary,
|
|
||||||
onSelected: onSelected,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
|
import '../../../../common/extension/extension.dart';
|
||||||
|
import '../../../../common/theme/theme.dart';
|
||||||
|
import '../../../../domain/analytic/analytic.dart';
|
||||||
|
|
||||||
|
class PurchaseVendorCard extends StatelessWidget {
|
||||||
|
final PurchasingVendorData data;
|
||||||
|
final int index;
|
||||||
|
final Animation<double> animation;
|
||||||
|
|
||||||
|
const PurchaseVendorCard({
|
||||||
|
super.key,
|
||||||
|
required this.data,
|
||||||
|
required this.index,
|
||||||
|
required this.animation,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.surface,
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: AppColor.border.withOpacity(0.25)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.04),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Rank badge
|
||||||
|
Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: AppColor.primaryGradient,
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${index + 1}',
|
||||||
|
style: AppStyle.sm.copyWith(
|
||||||
|
color: AppColor.textWhite,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Vendor icon
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColor.info.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.truck,
|
||||||
|
color: AppColor.info,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Name & details
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
data.vendorName.trim(),
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColor.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_Chip(
|
||||||
|
label: '${data.purchaseOrderCount} PO',
|
||||||
|
color: AppColor.info,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_Chip(
|
||||||
|
label: '${data.ingredientCount} bahan',
|
||||||
|
color: AppColor.secondary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_Chip(
|
||||||
|
label: '${data.quantity} pcs',
|
||||||
|
color: AppColor.warning,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Total cost
|
||||||
|
Text(
|
||||||
|
data.totalCost.currencyFormatRp,
|
||||||
|
style: AppStyle.md.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: AppColor.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Chip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const _Chip({required this.label, required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: AppStyle.xs.copyWith(
|
||||||
|
color: color,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -501,7 +501,7 @@ class PurchaseRoute extends _i26.PageRouteInfo<void> {
|
|||||||
static _i26.PageInfo page = _i26.PageInfo(
|
static _i26.PageInfo page = _i26.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i21.PurchasePage();
|
return _i26.WrappedRoute(child: const _i21.PurchasePage());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import connectivity_plus
|
|||||||
import device_info_plus
|
import device_info_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import firebase_core
|
import firebase_core
|
||||||
|
import firebase_crashlytics
|
||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import open_file_mac
|
import open_file_mac
|
||||||
@@ -23,6 +24,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
||||||
|
|||||||
@@ -481,6 +481,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.24.0"
|
version: "2.24.0"
|
||||||
|
firebase_crashlytics:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_crashlytics
|
||||||
|
sha256: "9897c01efaa950d2f6da8317d12452749a74dc45f33b46390a14cfe28067f271"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.7"
|
||||||
|
firebase_crashlytics_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_crashlytics_platform_interface
|
||||||
|
sha256: "16a71e08fbf6e00382816e1b13397898c29a54fa0ad969c2c2a3b82a704877f0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.35"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
+2
-1
@@ -3,7 +3,7 @@ description: "A new Flutter project."
|
|||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.1+2
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
@@ -50,6 +50,7 @@ dependencies:
|
|||||||
permission_handler: ^12.0.1
|
permission_handler: ^12.0.1
|
||||||
firebase_core: 2.32.0
|
firebase_core: 2.32.0
|
||||||
firebase_messaging: 14.7.10
|
firebase_messaging: 14.7.10
|
||||||
|
firebase_crashlytics: ^3.5.7
|
||||||
flutter_local_notifications: ^18.0.1
|
flutter_local_notifications: ^18.0.1
|
||||||
hugeicons: ^1.1.6
|
hugeicons: ^1.1.6
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user