feat: inventory report

This commit is contained in:
efrilm 2025-08-19 17:05:55 +07:00
parent 50934bfed9
commit 1aa65d1732
17 changed files with 1772 additions and 106 deletions

View File

@ -1,5 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<application
android:label="Apskel Owner"

View File

@ -0,0 +1,66 @@
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';
import '../../../domain/outlet/outlet.dart';
part 'inventory_report_event.dart';
part 'inventory_report_state.dart';
part 'inventory_report_bloc.freezed.dart';
@injectable
class InventoryReportBloc
extends Bloc<InventoryReportEvent, InventoryReportState> {
final IAnalyticRepository _analyticRepository;
final IOutletRepository _outletRepository;
InventoryReportBloc(this._analyticRepository, this._outletRepository)
: super(InventoryReportState.initial()) {
on<InventoryReportEvent>(_onInventoryReportEvent);
}
Future<void> _onInventoryReportEvent(
InventoryReportEvent event,
Emitter<InventoryReportState> emit,
) {
return event.map(
fetchedOutlet: (e) async {
emit(
state.copyWith(isFetchingOutlet: true, failureOptionOutlet: none()),
);
final result = await _outletRepository.currentOutlet();
var data = result.fold(
(f) => state.copyWith(failureOptionOutlet: optionOf(f)),
(currentOutlet) => state.copyWith(outlet: currentOutlet),
);
emit(data.copyWith(isFetchingOutlet: false));
},
fetchedInventory: (e) async {
emit(
state.copyWith(
isFetching: true,
failureOptionInventoryAnalytic: none(),
),
);
final result = await _analyticRepository.getInventory(
dateFrom: e.dateFrom,
dateTo: e.dateTo,
);
var data = result.fold(
(f) => state.copyWith(failureOptionInventoryAnalytic: optionOf(f)),
(inventoryAnalytic) =>
state.copyWith(inventoryAnalytic: inventoryAnalytic),
);
emit(data.copyWith(isFetching: false));
},
);
}
}

View File

@ -0,0 +1,645 @@
// 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 'inventory_report_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 _$InventoryReportEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetchedOutlet,
required TResult Function(DateTime dateFrom, DateTime dateTo)
fetchedInventory,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetchedOutlet,
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetchedOutlet,
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_FetchedOutlet value) fetchedOutlet,
required TResult Function(_FetchedInventory value) fetchedInventory,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
TResult? Function(_FetchedInventory value)? fetchedInventory,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_FetchedOutlet value)? fetchedOutlet,
TResult Function(_FetchedInventory value)? fetchedInventory,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $InventoryReportEventCopyWith<$Res> {
factory $InventoryReportEventCopyWith(
InventoryReportEvent value,
$Res Function(InventoryReportEvent) then,
) = _$InventoryReportEventCopyWithImpl<$Res, InventoryReportEvent>;
}
/// @nodoc
class _$InventoryReportEventCopyWithImpl<
$Res,
$Val extends InventoryReportEvent
>
implements $InventoryReportEventCopyWith<$Res> {
_$InventoryReportEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$FetchedOutletImplCopyWith<$Res> {
factory _$$FetchedOutletImplCopyWith(
_$FetchedOutletImpl value,
$Res Function(_$FetchedOutletImpl) then,
) = __$$FetchedOutletImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$FetchedOutletImplCopyWithImpl<$Res>
extends _$InventoryReportEventCopyWithImpl<$Res, _$FetchedOutletImpl>
implements _$$FetchedOutletImplCopyWith<$Res> {
__$$FetchedOutletImplCopyWithImpl(
_$FetchedOutletImpl _value,
$Res Function(_$FetchedOutletImpl) _then,
) : super(_value, _then);
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$FetchedOutletImpl implements _FetchedOutlet {
const _$FetchedOutletImpl();
@override
String toString() {
return 'InventoryReportEvent.fetchedOutlet()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$FetchedOutletImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetchedOutlet,
required TResult Function(DateTime dateFrom, DateTime dateTo)
fetchedInventory,
}) {
return fetchedOutlet();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetchedOutlet,
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
}) {
return fetchedOutlet?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetchedOutlet,
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
required TResult orElse(),
}) {
if (fetchedOutlet != null) {
return fetchedOutlet();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_FetchedOutlet value) fetchedOutlet,
required TResult Function(_FetchedInventory value) fetchedInventory,
}) {
return fetchedOutlet(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
TResult? Function(_FetchedInventory value)? fetchedInventory,
}) {
return fetchedOutlet?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_FetchedOutlet value)? fetchedOutlet,
TResult Function(_FetchedInventory value)? fetchedInventory,
required TResult orElse(),
}) {
if (fetchedOutlet != null) {
return fetchedOutlet(this);
}
return orElse();
}
}
abstract class _FetchedOutlet implements InventoryReportEvent {
const factory _FetchedOutlet() = _$FetchedOutletImpl;
}
/// @nodoc
abstract class _$$FetchedInventoryImplCopyWith<$Res> {
factory _$$FetchedInventoryImplCopyWith(
_$FetchedInventoryImpl value,
$Res Function(_$FetchedInventoryImpl) then,
) = __$$FetchedInventoryImplCopyWithImpl<$Res>;
@useResult
$Res call({DateTime dateFrom, DateTime dateTo});
}
/// @nodoc
class __$$FetchedInventoryImplCopyWithImpl<$Res>
extends _$InventoryReportEventCopyWithImpl<$Res, _$FetchedInventoryImpl>
implements _$$FetchedInventoryImplCopyWith<$Res> {
__$$FetchedInventoryImplCopyWithImpl(
_$FetchedInventoryImpl _value,
$Res Function(_$FetchedInventoryImpl) _then,
) : super(_value, _then);
/// Create a copy of InventoryReportEvent
/// 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(
_$FetchedInventoryImpl(
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 _$FetchedInventoryImpl implements _FetchedInventory {
const _$FetchedInventoryImpl(this.dateFrom, this.dateTo);
@override
final DateTime dateFrom;
@override
final DateTime dateTo;
@override
String toString() {
return 'InventoryReportEvent.fetchedInventory(dateFrom: $dateFrom, dateTo: $dateTo)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FetchedInventoryImpl &&
(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 InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FetchedInventoryImplCopyWith<_$FetchedInventoryImpl> get copyWith =>
__$$FetchedInventoryImplCopyWithImpl<_$FetchedInventoryImpl>(
this,
_$identity,
);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetchedOutlet,
required TResult Function(DateTime dateFrom, DateTime dateTo)
fetchedInventory,
}) {
return fetchedInventory(dateFrom, dateTo);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetchedOutlet,
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
}) {
return fetchedInventory?.call(dateFrom, dateTo);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetchedOutlet,
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedInventory,
required TResult orElse(),
}) {
if (fetchedInventory != null) {
return fetchedInventory(dateFrom, dateTo);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_FetchedOutlet value) fetchedOutlet,
required TResult Function(_FetchedInventory value) fetchedInventory,
}) {
return fetchedInventory(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
TResult? Function(_FetchedInventory value)? fetchedInventory,
}) {
return fetchedInventory?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_FetchedOutlet value)? fetchedOutlet,
TResult Function(_FetchedInventory value)? fetchedInventory,
required TResult orElse(),
}) {
if (fetchedInventory != null) {
return fetchedInventory(this);
}
return orElse();
}
}
abstract class _FetchedInventory implements InventoryReportEvent {
const factory _FetchedInventory(
final DateTime dateFrom,
final DateTime dateTo,
) = _$FetchedInventoryImpl;
DateTime get dateFrom;
DateTime get dateTo;
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FetchedInventoryImplCopyWith<_$FetchedInventoryImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$InventoryReportState {
InventoryAnalytic get inventoryAnalytic => throw _privateConstructorUsedError;
Option<AnalyticFailure> get failureOptionInventoryAnalytic =>
throw _privateConstructorUsedError;
Outlet get outlet => throw _privateConstructorUsedError;
Option<OutletFailure> get failureOptionOutlet =>
throw _privateConstructorUsedError;
bool get isFetching => throw _privateConstructorUsedError;
bool get isFetchingOutlet => throw _privateConstructorUsedError;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$InventoryReportStateCopyWith<InventoryReportState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $InventoryReportStateCopyWith<$Res> {
factory $InventoryReportStateCopyWith(
InventoryReportState value,
$Res Function(InventoryReportState) then,
) = _$InventoryReportStateCopyWithImpl<$Res, InventoryReportState>;
@useResult
$Res call({
InventoryAnalytic inventoryAnalytic,
Option<AnalyticFailure> failureOptionInventoryAnalytic,
Outlet outlet,
Option<OutletFailure> failureOptionOutlet,
bool isFetching,
bool isFetchingOutlet,
});
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic;
$OutletCopyWith<$Res> get outlet;
}
/// @nodoc
class _$InventoryReportStateCopyWithImpl<
$Res,
$Val extends InventoryReportState
>
implements $InventoryReportStateCopyWith<$Res> {
_$InventoryReportStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? inventoryAnalytic = null,
Object? failureOptionInventoryAnalytic = null,
Object? outlet = null,
Object? failureOptionOutlet = null,
Object? isFetching = null,
Object? isFetchingOutlet = null,
}) {
return _then(
_value.copyWith(
inventoryAnalytic: null == inventoryAnalytic
? _value.inventoryAnalytic
: inventoryAnalytic // ignore: cast_nullable_to_non_nullable
as InventoryAnalytic,
failureOptionInventoryAnalytic:
null == failureOptionInventoryAnalytic
? _value.failureOptionInventoryAnalytic
: failureOptionInventoryAnalytic // ignore: cast_nullable_to_non_nullable
as Option<AnalyticFailure>,
outlet: null == outlet
? _value.outlet
: outlet // ignore: cast_nullable_to_non_nullable
as Outlet,
failureOptionOutlet: null == failureOptionOutlet
? _value.failureOptionOutlet
: failureOptionOutlet // ignore: cast_nullable_to_non_nullable
as Option<OutletFailure>,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
isFetchingOutlet: null == isFetchingOutlet
? _value.isFetchingOutlet
: isFetchingOutlet // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic {
return $InventoryAnalyticCopyWith<$Res>(_value.inventoryAnalytic, (value) {
return _then(_value.copyWith(inventoryAnalytic: value) as $Val);
});
}
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OutletCopyWith<$Res> get outlet {
return $OutletCopyWith<$Res>(_value.outlet, (value) {
return _then(_value.copyWith(outlet: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$InventoryReportStateImplCopyWith<$Res>
implements $InventoryReportStateCopyWith<$Res> {
factory _$$InventoryReportStateImplCopyWith(
_$InventoryReportStateImpl value,
$Res Function(_$InventoryReportStateImpl) then,
) = __$$InventoryReportStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
InventoryAnalytic inventoryAnalytic,
Option<AnalyticFailure> failureOptionInventoryAnalytic,
Outlet outlet,
Option<OutletFailure> failureOptionOutlet,
bool isFetching,
bool isFetchingOutlet,
});
@override
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic;
@override
$OutletCopyWith<$Res> get outlet;
}
/// @nodoc
class __$$InventoryReportStateImplCopyWithImpl<$Res>
extends _$InventoryReportStateCopyWithImpl<$Res, _$InventoryReportStateImpl>
implements _$$InventoryReportStateImplCopyWith<$Res> {
__$$InventoryReportStateImplCopyWithImpl(
_$InventoryReportStateImpl _value,
$Res Function(_$InventoryReportStateImpl) _then,
) : super(_value, _then);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? inventoryAnalytic = null,
Object? failureOptionInventoryAnalytic = null,
Object? outlet = null,
Object? failureOptionOutlet = null,
Object? isFetching = null,
Object? isFetchingOutlet = null,
}) {
return _then(
_$InventoryReportStateImpl(
inventoryAnalytic: null == inventoryAnalytic
? _value.inventoryAnalytic
: inventoryAnalytic // ignore: cast_nullable_to_non_nullable
as InventoryAnalytic,
failureOptionInventoryAnalytic: null == failureOptionInventoryAnalytic
? _value.failureOptionInventoryAnalytic
: failureOptionInventoryAnalytic // ignore: cast_nullable_to_non_nullable
as Option<AnalyticFailure>,
outlet: null == outlet
? _value.outlet
: outlet // ignore: cast_nullable_to_non_nullable
as Outlet,
failureOptionOutlet: null == failureOptionOutlet
? _value.failureOptionOutlet
: failureOptionOutlet // ignore: cast_nullable_to_non_nullable
as Option<OutletFailure>,
isFetching: null == isFetching
? _value.isFetching
: isFetching // ignore: cast_nullable_to_non_nullable
as bool,
isFetchingOutlet: null == isFetchingOutlet
? _value.isFetchingOutlet
: isFetchingOutlet // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$InventoryReportStateImpl implements _InventoryReportState {
const _$InventoryReportStateImpl({
required this.inventoryAnalytic,
required this.failureOptionInventoryAnalytic,
required this.outlet,
required this.failureOptionOutlet,
this.isFetching = false,
this.isFetchingOutlet = false,
});
@override
final InventoryAnalytic inventoryAnalytic;
@override
final Option<AnalyticFailure> failureOptionInventoryAnalytic;
@override
final Outlet outlet;
@override
final Option<OutletFailure> failureOptionOutlet;
@override
@JsonKey()
final bool isFetching;
@override
@JsonKey()
final bool isFetchingOutlet;
@override
String toString() {
return 'InventoryReportState(inventoryAnalytic: $inventoryAnalytic, failureOptionInventoryAnalytic: $failureOptionInventoryAnalytic, outlet: $outlet, failureOptionOutlet: $failureOptionOutlet, isFetching: $isFetching, isFetchingOutlet: $isFetchingOutlet)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$InventoryReportStateImpl &&
(identical(other.inventoryAnalytic, inventoryAnalytic) ||
other.inventoryAnalytic == inventoryAnalytic) &&
(identical(
other.failureOptionInventoryAnalytic,
failureOptionInventoryAnalytic,
) ||
other.failureOptionInventoryAnalytic ==
failureOptionInventoryAnalytic) &&
(identical(other.outlet, outlet) || other.outlet == outlet) &&
(identical(other.failureOptionOutlet, failureOptionOutlet) ||
other.failureOptionOutlet == failureOptionOutlet) &&
(identical(other.isFetching, isFetching) ||
other.isFetching == isFetching) &&
(identical(other.isFetchingOutlet, isFetchingOutlet) ||
other.isFetchingOutlet == isFetchingOutlet));
}
@override
int get hashCode => Object.hash(
runtimeType,
inventoryAnalytic,
failureOptionInventoryAnalytic,
outlet,
failureOptionOutlet,
isFetching,
isFetchingOutlet,
);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$InventoryReportStateImplCopyWith<_$InventoryReportStateImpl>
get copyWith =>
__$$InventoryReportStateImplCopyWithImpl<_$InventoryReportStateImpl>(
this,
_$identity,
);
}
abstract class _InventoryReportState implements InventoryReportState {
const factory _InventoryReportState({
required final InventoryAnalytic inventoryAnalytic,
required final Option<AnalyticFailure> failureOptionInventoryAnalytic,
required final Outlet outlet,
required final Option<OutletFailure> failureOptionOutlet,
final bool isFetching,
final bool isFetchingOutlet,
}) = _$InventoryReportStateImpl;
@override
InventoryAnalytic get inventoryAnalytic;
@override
Option<AnalyticFailure> get failureOptionInventoryAnalytic;
@override
Outlet get outlet;
@override
Option<OutletFailure> get failureOptionOutlet;
@override
bool get isFetching;
@override
bool get isFetchingOutlet;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$InventoryReportStateImplCopyWith<_$InventoryReportStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,10 @@
part of 'inventory_report_bloc.dart';
@freezed
class InventoryReportEvent with _$InventoryReportEvent {
const factory InventoryReportEvent.fetchedOutlet() = _FetchedOutlet;
const factory InventoryReportEvent.fetchedInventory(
DateTime dateFrom,
DateTime dateTo,
) = _FetchedInventory;
}

View File

@ -0,0 +1,20 @@
part of 'inventory_report_bloc.dart';
@freezed
class InventoryReportState with _$InventoryReportState {
const factory InventoryReportState({
required InventoryAnalytic inventoryAnalytic,
required Option<AnalyticFailure> failureOptionInventoryAnalytic,
required Outlet outlet,
required Option<OutletFailure> failureOptionOutlet,
@Default(false) bool isFetching,
@Default(false) bool isFetchingOutlet,
}) = _InventoryReportState;
factory InventoryReportState.initial() => InventoryReportState(
inventoryAnalytic: InventoryAnalytic.empty(),
failureOptionInventoryAnalytic: none(),
outlet: Outlet.empty(),
failureOptionOutlet: none(),
);
}

View File

@ -0,0 +1,79 @@
import 'dart:developer';
import 'dart:io';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/widgets.dart';
class HelperPdfService {
static Future<File> saveDocument({
required String name,
required Document pdf,
}) async {
try {
log("Starting PDF save process for: $name");
log("PDF document object: $pdf");
final bytes = await pdf.save();
log("PDF bytes generated successfully, size: ${bytes.length} bytes");
if (bytes.isEmpty) {
log("WARNING: PDF bytes are empty!");
return Future.error("PDF bytes are empty");
}
final dir = await getApplicationDocumentsDirectory();
log("Documents directory: ${dir.path}");
final file = File('${dir.path}/$name');
log("Saving PDF to: ${file.path}");
await file.writeAsBytes(bytes);
log("PDF saved successfully to: ${file.path}");
// Verify file was created
if (await file.exists()) {
final fileSize = await file.length();
log("File exists and size is: $fileSize bytes");
} else {
log("ERROR: File was not created!");
return Future.error("File was not created");
}
return file;
} catch (e) {
log("Failed to save document: $e");
log("Error stack trace: ${StackTrace.current}");
return Future.error("Failed to save document: $e");
}
}
static Future openFile(File file) async {
try {
final url = file.path;
log("Attempting to open file: $url");
if (!await file.exists()) {
log("ERROR: File does not exist: $url");
return;
}
final fileSize = await file.length();
log("File exists and size is: $fileSize bytes");
log("Calling OpenFile.open...");
final result = await OpenFile.open(url, type: "application/pdf");
log("OpenFile result: $result");
if (result.type == ResultType.done) {
log("File opened successfully");
} else {
log("File opening failed with result: ${result.type}");
log("Error message: ${result.message}");
}
} catch (e) {
log("Failed to open file: $e");
log("Error stack trace: ${StackTrace.current}");
}
}
}

View File

@ -0,0 +1,72 @@
import 'dart:developer';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
class PermessionHelper {
Future<bool> checkPermission() async {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
bool permissionStatus;
if (deviceInfo.version.sdkInt > 32) {
permissionStatus = await Permission.photos.request().isGranted;
} else {
permissionStatus = await Permission.storage.request().isGranted;
}
if (permissionStatus) {
log('Izin penyimpanan sudah diberikan.');
} else {
if (deviceInfo.version.sdkInt > 32) {
log('deviceInfo.version.sdkInt > 32.');
permissionStatus = await Permission.photos.request().isGranted;
} else {
permissionStatus = await Permission.storage.request().isGranted;
}
// } else {
// openAppSettings();
// }
}
log('permissionStatus: $permissionStatus');
return permissionStatus;
}
void permessionPrinter() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetooth,
Permission.bluetoothScan,
Permission.bluetoothAdvertise,
Permission.bluetoothConnect,
].request();
log("statuses: $statuses");
}
}
// try {
// final status =
// await PermessionHelper().checkPermission();
// if (status) {
// final pdfFile = await InventoryReport.previewPdf(
// searchDateFormatted: widget.searchDateFormatted,
// inventory: widget.inventory,
// );
// log("pdfFile: $pdfFile");
// await HelperPdfService.openFile(pdfFile);
// } else {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text(
// 'Storage permission is required to save PDF'),
// backgroundColor: Colors.red,
// ),
// );
// }
// } catch (e) {
// log("Error generating PDF: $e");
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Failed to generate PDF: $e'),
// backgroundColor: Colors.red,
// ),
// );
// }

View File

@ -41,6 +41,8 @@ import 'package:apskel_owner_flutter/application/outlet/current_outlet_loader/cu
as _i337;
import 'package:apskel_owner_flutter/application/product/product_loader/product_loader_bloc.dart'
as _i458;
import 'package:apskel_owner_flutter/application/report/inventory_report/inventory_report_bloc.dart'
as _i346;
import 'package:apskel_owner_flutter/application/user/change_password_form/change_password_form_bloc.dart'
as _i1030;
import 'package:apskel_owner_flutter/application/user/user_edit_form/user_edit_form_bloc.dart'
@ -263,6 +265,12 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i1030.ChangePasswordFormBloc>(
() => _i1030.ChangePasswordFormBloc(gh<_i635.IUserRepository>()),
);
gh.factory<_i346.InventoryReportBloc>(
() => _i346.InventoryReportBloc(
gh<_i477.IAnalyticRepository>(),
gh<_i197.IOutletRepository>(),
),
);
return this;
}
}

View File

@ -0,0 +1,547 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import '../../../common/utils/pdf_service.dart';
import '../../../domain/analytic/analytic.dart';
import '../../../domain/outlet/outlet.dart';
class InventoryReport {
static final primaryColor = PdfColor.fromHex("36175e");
static Future<File> previewPdf({
required String searchDateFormatted,
required InventoryAnalytic inventory,
required Outlet outlet,
}) async {
final pdf = pw.Document();
final ByteData dataImage = await rootBundle.load('assets/images/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List();
final image = pw.MemoryImage(bytes);
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: pw.EdgeInsets.zero,
build: (pw.Context context) {
return [
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Bagian kiri - Logo dan Info Perusahaan
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
pw.Container(
width: 40,
height: 40,
child: pw.Image(image),
),
pw.SizedBox(width: 15),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Apskel',
style: pw.TextStyle(
fontSize: 28,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 4),
pw.Text(
outlet.name,
style: pw.TextStyle(
fontSize: 16,
color: PdfColors.grey700,
),
),
pw.SizedBox(height: 2),
pw.Text(
outlet.address,
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey600,
),
),
],
),
],
),
// Bagian kanan - Info Laporan
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text(
'Laporan Transaksi',
style: pw.TextStyle(
fontSize: 24,
fontWeight: pw.FontWeight.bold,
color: PdfColors.grey800,
),
),
pw.SizedBox(height: 8),
pw.Text(
searchDateFormatted,
style: pw.TextStyle(
fontSize: 14,
color: PdfColors.grey600,
),
),
pw.SizedBox(height: 4),
pw.Text(
'Laporan',
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey500,
),
),
],
),
],
),
),
pw.Container(
width: double.infinity,
height: 3,
color: primaryColor,
),
// Summary
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('1. Ringkasan'),
pw.SizedBox(height: 30),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Item',
(inventory.summary.totalProducts).toString(),
),
_buildSummaryItem(
'Total Item Masuk',
(inventory.products.fold<num>(
0,
(sum, item) => sum + (item.totalIn),
)).toString(),
),
_buildSummaryItem(
'Total Item Keluar',
(inventory.products.fold<num>(
0,
(sum, item) => sum + (item.totalOut),
)).toString(),
),
],
),
),
pw.SizedBox(width: 20),
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Ingredient',
(inventory.summary.totalIngredients).toString(),
),
_buildSummaryItem(
'Total Ingredient Masuk',
(inventory.ingredients.fold<num>(
0,
(sum, item) => sum + (item.totalIn),
)).toString(),
),
_buildSummaryItem(
'Total Ingredient Keluar',
(inventory.ingredients.fold<num>(
0,
(sum, item) => sum + (item.totalOut),
)).toString(),
),
],
),
),
],
),
],
),
),
// Summary Item
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('2. Item'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Kategori'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(color: PdfColors.white),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: inventory.products
.map(
(item) => _buildProductDataRow(
item,
inventory.products.indexOf(item) % 2 == 0,
),
)
.toList(),
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell(
(inventory.products.fold<num>(
0,
(sum, item) => sum + (item.quantity),
)).toString(),
),
_buildTotalCell(
(inventory.products.fold<num>(
0,
(sum, item) => sum + (item.totalIn),
)).toString(),
),
_buildTotalCell(
(inventory.products.fold<num>(
0,
(sum, item) => sum + (item.totalOut),
)).toString(),
),
],
),
],
),
),
],
),
],
),
),
// Summary Ingredient
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('3. Ingredient'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(color: PdfColors.white),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: inventory.ingredients
.map(
(item) => _buildIngredientsDataRow(
item,
inventory.ingredients.indexOf(item) % 2 == 0,
),
)
.toList(),
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
(inventory.ingredients.fold<num>(
0,
(sum, item) => sum + (item.quantity),
)).toString(),
),
_buildTotalCell(
(inventory.ingredients.fold<num>(
0,
(sum, item) => sum + (item.totalIn),
)).toString(),
),
_buildTotalCell(
(inventory.ingredients.fold<num>(
0,
(sum, item) => sum + (item.totalOut),
)).toString(),
),
],
),
],
),
),
],
),
],
),
),
];
},
),
);
return HelperPdfService.saveDocument(
name:
'Apskel POS | Inventory Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf,
);
}
static pw.Widget _buildSectionWidget(String title) {
return pw.Text(
title,
style: pw.TextStyle(
fontSize: 20,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
);
}
static pw.Widget _buildSummaryItem(
String label,
String value, {
pw.TextStyle? valueStyle,
pw.TextStyle? labelStyle,
}) {
return pw.Container(
padding: pw.EdgeInsets.only(bottom: 8),
margin: pw.EdgeInsets.only(bottom: 16),
decoration: pw.BoxDecoration(
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.grey300)),
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(label, style: labelStyle),
pw.Text(
value,
style: valueStyle ?? pw.TextStyle(fontWeight: pw.FontWeight.bold),
),
],
),
);
}
static pw.Widget _buildHeaderCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.Widget _buildDataCell(
String text, {
pw.Alignment alignment = pw.Alignment.center,
PdfColor? textColor,
}) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
alignment: alignment,
child: pw.Text(
text,
style: pw.TextStyle(
fontSize: 12,
color: textColor ?? PdfColors.black,
fontWeight: pw.FontWeight.normal,
),
textAlign: alignment == pw.Alignment.centerLeft
? pw.TextAlign.left
: pw.TextAlign.center,
),
);
}
static pw.Widget _buildTotalCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.TableRow _buildProductDataRow(
InventoryProduct product,
bool isEven,
) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: product.isZeroStock
? PdfColors.red100
: product.isLowStock
? PdfColors.yellow100
: isEven
? PdfColors.grey50
: PdfColors.white,
),
children: [
_buildDataCell(product.productName, alignment: pw.Alignment.centerLeft),
_buildDataCell(
product.categoryName,
alignment: pw.Alignment.centerLeft,
),
_buildDataCell(product.quantity.toString()),
_buildDataCell(product.totalIn.toString()),
_buildDataCell(product.totalOut.toString()),
],
);
}
static pw.TableRow _buildIngredientsDataRow(
InventoryIngredient item,
bool isEven,
) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: item.isZeroStock
? PdfColors.red100
: item.isLowStock
? PdfColors.yellow100
: isEven
? PdfColors.grey50
: PdfColors.white,
),
children: [
_buildDataCell(item.ingredientName, alignment: pw.Alignment.centerLeft),
_buildDataCell(item.quantity.toString()),
_buildDataCell(item.totalIn.toString()),
_buildDataCell(item.totalOut.toString()),
],
);
}
}

View File

@ -1,16 +1,34 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../../application/report/inventory_report/inventory_report_bloc.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../../../common/utils/pdf_service.dart';
import '../../../common/utils/permission.dart';
import '../../../injection.dart';
import '../../components/appbar/appbar.dart';
import '../../components/field/date_range_picker_field.dart';
import '../../components/report/inventory_report.dart';
import '../../components/toast/flushbar.dart';
@RoutePage()
class DownloadReportPage extends StatefulWidget {
class DownloadReportPage extends StatefulWidget implements AutoRouteWrapper {
const DownloadReportPage({super.key});
@override
State<DownloadReportPage> createState() => _DownloadReportPageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) =>
getIt<InventoryReportBloc>()..add(InventoryReportEvent.fetchedOutlet()),
child: this,
);
}
class _DownloadReportPageState extends State<DownloadReportPage>
@ -26,10 +44,10 @@ class _DownloadReportPageState extends State<DownloadReportPage>
DateTime? _transactionEndDate;
DateTime? _inventoryStartDate;
DateTime? _inventoryEndDate;
DateTime? _salesStartDate;
DateTime? _salesEndDate;
DateTime? _customerStartDate;
DateTime? _customerEndDate;
// DateTime? _salesStartDate;
// DateTime? _salesEndDate;
// DateTime? _customerStartDate;
// DateTime? _customerEndDate;
@override
void initState() {
@ -76,28 +94,9 @@ class _DownloadReportPageState extends State<DownloadReportPage>
DateTime? endDate,
) {
if (startDate == null || endDate == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select both start and end dates'),
backgroundColor: AppColor.error,
),
);
AppFlushbar.showError(context, 'Please select both start and end dates');
return;
}
// Implement download logic here
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Downloading $reportType from ${_formatDate(startDate)} to ${_formatDate(endDate)}',
),
backgroundColor: AppColor.success,
),
);
}
String _formatDate(DateTime date) {
return '${date.day}/${date.month}/${date.year}';
}
@override
@ -139,6 +138,7 @@ class _DownloadReportPageState extends State<DownloadReportPage>
],
startDate: _transactionStartDate,
endDate: _transactionEndDate,
isLoading: false,
onDateRangeChanged: (start, end) {
setState(() {
_transactionStartDate = start;
@ -156,79 +156,115 @@ class _DownloadReportPageState extends State<DownloadReportPage>
const SizedBox(height: 20),
// Inventory Report Card
_ReportOptionCard(
title: 'Inventory Report',
subtitle:
'Export inventory and stock data with trends',
icon: Icons.inventory_2_outlined,
gradient: const [
AppColor.secondary,
AppColor.secondaryLight,
],
startDate: _inventoryStartDate,
endDate: _inventoryEndDate,
onDateRangeChanged: (start, end) {
setState(() {
_inventoryStartDate = start;
_inventoryEndDate = end;
});
BlocBuilder<InventoryReportBloc, InventoryReportState>(
builder: (context, state) {
return _ReportOptionCard(
title: 'Inventory Report',
subtitle:
'Export inventory and stock data with trends',
icon: Icons.inventory_2_outlined,
gradient: const [
AppColor.secondary,
AppColor.secondaryLight,
],
startDate: _inventoryStartDate,
endDate: _inventoryEndDate,
isLoading: state.isFetching,
onDateRangeChanged: (start, end) {
setState(() {
_inventoryStartDate = start;
_inventoryEndDate = end;
});
if (start != null || end != null) {
context.read<InventoryReportBloc>().add(
InventoryReportEvent.fetchedInventory(
start!,
end!,
),
);
}
},
onDownload: () async {
try {
final status = await PermessionHelper()
.checkPermission();
if (status) {
final pdfFile =
await InventoryReport.previewPdf(
searchDateFormatted:
"${_inventoryStartDate?.toServerDate} - ${_inventoryEndDate?.toServerDate}",
inventory: state.inventoryAnalytic,
outlet: state.outlet,
);
log("pdfFile: $pdfFile");
await HelperPdfService.openFile(pdfFile);
} else {
AppFlushbar.showError(
context,
'Storage permission is required to save PDF',
);
}
} catch (e) {
log("Error generating PDF: $e");
AppFlushbar.showError(
context,
'Failed to generate PDF: $e',
);
}
},
delay: 400,
);
},
onDownload: () => _downloadReport(
'Inventory Report',
_inventoryStartDate,
_inventoryEndDate,
),
delay: 400,
),
const SizedBox(height: 20),
// Sales Report Card
_ReportOptionCard(
title: 'Sales Report',
subtitle: 'Export sales performance and revenue data',
icon: Icons.trending_up_outlined,
gradient: const [AppColor.info, Color(0xFF64B5F6)],
startDate: _salesStartDate,
endDate: _salesEndDate,
onDateRangeChanged: (start, end) {
setState(() {
_salesStartDate = start;
_salesEndDate = end;
});
},
onDownload: () => _downloadReport(
'Sales Report',
_salesStartDate,
_salesEndDate,
),
delay: 600,
),
// _ReportOptionCard(
// title: 'Sales Report',
// subtitle: 'Export sales performance and revenue data',
// icon: Icons.trending_up_outlined,
// gradient: const [AppColor.info, Color(0xFF64B5F6)],
// startDate: _salesStartDate,
// endDate: _salesEndDate,
// onDateRangeChanged: (start, end) {
// setState(() {
// _salesStartDate = start;
// _salesEndDate = end;
// });
// },
// onDownload: () => _downloadReport(
// 'Sales Report',
// _salesStartDate,
// _salesEndDate,
// ),
// delay: 600,
// ),
const SizedBox(height: 20),
// const SizedBox(height: 20),
// Customer Report Card
_ReportOptionCard(
title: 'Customer Report',
subtitle:
'Export customer data and behavior analytics',
icon: Icons.people_outline,
gradient: const [AppColor.warning, Color(0xFFFFB74D)],
startDate: _customerStartDate,
endDate: _customerEndDate,
onDateRangeChanged: (start, end) {
setState(() {
_customerStartDate = start;
_customerEndDate = end;
});
},
onDownload: () => _downloadReport(
'Customer Report',
_customerStartDate,
_customerEndDate,
),
delay: 800,
),
// // Customer Report Card
// _ReportOptionCard(
// title: 'Customer Report',
// subtitle:
// 'Export customer data and behavior analytics',
// icon: Icons.people_outline,
// gradient: const [AppColor.warning, Color(0xFFFFB74D)],
// startDate: _customerStartDate,
// endDate: _customerEndDate,
// onDateRangeChanged: (start, end) {
// setState(() {
// _customerStartDate = start;
// _customerEndDate = end;
// });
// },
// onDownload: () => _downloadReport(
// 'Customer Report',
// _customerStartDate,
// _customerEndDate,
// ),
// delay: 800,
// ),
],
),
),
@ -253,6 +289,7 @@ class _ReportOptionCard extends StatefulWidget {
final DateTime? endDate;
final Function(DateTime? startDate, DateTime? endDate) onDateRangeChanged;
final VoidCallback onDownload;
final bool isLoading;
final int delay;
const _ReportOptionCard({
@ -265,6 +302,7 @@ class _ReportOptionCard extends StatefulWidget {
required this.onDateRangeChanged,
required this.onDownload,
required this.delay,
required this.isLoading,
});
@override
@ -431,23 +469,41 @@ class _ReportOptionCardState extends State<_ReportOptionCard>
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.download_rounded,
color: widget.gradient.first,
),
const SizedBox(width: 8),
Text(
'Download Report',
style: AppStyle.md.copyWith(
color: widget.gradient.first,
fontWeight: FontWeight.bold,
child: widget.isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SpinKitCircle(
color: widget.gradient.first,
size: 24,
),
const SizedBox(width: 8),
Text(
'Loading',
style: AppStyle.md.copyWith(
color: widget.gradient.first,
fontWeight: FontWeight.bold,
),
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.download_rounded,
color: widget.gradient.first,
),
const SizedBox(width: 8),
Text(
'Download Report',
style: AppStyle.md.copyWith(
color: widget.gradient.first,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
),
],

View File

@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <open_file_linux/open_file_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
open_file_linux
url_launcher_linux
)

View File

@ -8,6 +8,7 @@ import Foundation
import connectivity_plus
import device_info_plus
import file_selector_macos
import open_file_mac
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
@ -18,6 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@ -81,6 +81,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.0.4"
barcode:
dependency: transitive
description:
name: barcode
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
bidi:
dependency: transitive
description:
name: bidi
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
bloc:
dependency: transitive
description:
@ -869,6 +885,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
open_file:
dependency: "direct main"
description:
name: open_file
sha256: d17e2bddf5b278cb2ae18393d0496aa4f162142ba97d1a9e0c30d476adf99c0e
url: "https://pub.dev"
source: hosted
version: "3.5.10"
open_file_android:
dependency: transitive
description:
name: open_file_android
sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
open_file_ios:
dependency: transitive
description:
name: open_file_ios
sha256: "02996f01e5f6863832068e97f8f3a5ef9b613516db6897f373b43b79849e4d07"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
open_file_linux:
dependency: transitive
description:
name: open_file_linux
sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550
url: "https://pub.dev"
source: hosted
version: "0.0.5"
open_file_mac:
dependency: transitive
description:
name: open_file_mac
sha256: "1440b1e37ceb0642208cfeb2c659c6cda27b25187a90635c9d1acb7d0584d324"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
open_file_platform_interface:
dependency: transitive
description:
name: open_file_platform_interface
sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
open_file_web:
dependency: transitive
description:
name: open_file_web
sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f
url: "https://pub.dev"
source: hosted
version: "0.0.4"
open_file_windows:
dependency: transitive
description:
name: open_file_windows
sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875
url: "https://pub.dev"
source: hosted
version: "0.0.3"
package_config:
dependency: transitive
description:
@ -957,6 +1037,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
pdf:
dependency: "direct main"
description:
name: pdf
sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
url: "https://pub.dev"
source: hosted
version: "3.11.3"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
@ -1021,6 +1157,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
recase:
dependency: transitive
description:

View File

@ -45,6 +45,9 @@ dependencies:
syncfusion_flutter_datepicker: ^30.2.5
url_launcher: ^6.3.2
device_info_plus: ^11.5.0
pdf: ^3.11.3
open_file: ^3.5.10
permission_handler: ^12.0.1
dev_dependencies:
flutter_test:

View File

@ -8,6 +8,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
file_selector_windows
permission_handler_windows
url_launcher_windows
)