apskel-owner-flutter/lib/presentation/pages/download/download_report_page.dart
2025-08-20 13:52:49 +07:00

565 lines
22 KiB
Dart

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 '../../../application/report/transaction_report/transaction_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/report/transaction_report.dart';
import '../../components/toast/flushbar.dart';
@RoutePage()
class DownloadReportPage extends StatefulWidget implements AutoRouteWrapper {
const DownloadReportPage({super.key});
@override
State<DownloadReportPage> createState() => _DownloadReportPageState();
@override
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
getIt<InventoryReportBloc>()
..add(InventoryReportEvent.fetchedOutlet()),
),
BlocProvider(
create: (context) =>
getIt<TransactionReportBloc>()
..add(TransactionReportEvent.fetchedOutlet()),
),
],
child: this,
);
}
class _DownloadReportPageState extends State<DownloadReportPage>
with TickerProviderStateMixin {
late AnimationController _fadeController;
late AnimationController _slideController;
late AnimationController _scaleController;
late Animation<double> _fadeAnimation;
// Date range variables for each report type
DateTime? _transactionStartDate;
DateTime? _transactionEndDate;
DateTime? _inventoryStartDate;
DateTime? _inventoryEndDate;
// DateTime? _salesStartDate;
// DateTime? _salesEndDate;
// DateTime? _customerStartDate;
// DateTime? _customerEndDate;
@override
void initState() {
super.initState();
// Initialize animation controllers
_fadeController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_slideController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_scaleController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
// Initialize animations
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
);
// Start animations
_fadeController.forward();
_slideController.forward();
_scaleController.forward();
}
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
_scaleController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: CustomScrollView(
slivers: [
// SliverAppBar with gradient
SliverAppBar(
expandedHeight: 120,
floating: false,
pinned: true,
elevation: 0,
backgroundColor: AppColor.primary,
flexibleSpace: CustomAppBar(title: context.lang.download_report),
),
// Content
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Report Options
FadeTransition(
opacity: _fadeAnimation,
child: Column(
children: [
// Transaction Report Card
BlocBuilder<
TransactionReportBloc,
TransactionReportState
>(
builder: (context, state) {
return _ReportOptionCard(
title: context.lang.transaction_report,
subtitle: context.lang.transaction_report_desc,
icon: Icons.receipt_long_outlined,
gradient: const [
AppColor.primary,
AppColor.primaryLight,
],
startDate: _transactionStartDate,
endDate: _transactionEndDate,
isLoading: state.isFetching,
onDateRangeChanged: (start, end) {
setState(() {
_transactionStartDate = start;
_transactionEndDate = end;
});
if (start != null || end != null) {
context.read<TransactionReportBloc>().add(
TransactionReportEvent.fetchedTransaction(
start!,
end!,
),
);
}
},
onDownload: () async {
try {
final status = await PermessionHelper()
.checkPermission();
if (status) {
final pdfFile =
await TransactionReport.previewPdf(
searchDateFormatted:
"${_transactionStartDate?.toServerDate} - ${_transactionEndDate?.toServerDate}",
outlet: state.outlet,
categoryAnalyticData:
state.categoryAnalytic,
profitLossData:
state.profitLossAnalytic,
paymentMethodAnalyticData:
state.paymentMethodAnalytic,
productAnalyticData:
state.productAnalytic,
);
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: 200,
);
},
),
const SizedBox(height: 20),
// Inventory Report Card
BlocBuilder<InventoryReportBloc, InventoryReportState>(
builder: (context, state) {
return _ReportOptionCard(
title: context.lang.invetory_report,
subtitle: context.lang.invetory_report_desc,
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,
);
},
),
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,
// ),
// 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,
// ),
],
),
),
const SizedBox(height: 40),
],
),
),
),
],
),
);
}
}
class _ReportOptionCard extends StatefulWidget {
final String title;
final String subtitle;
final IconData icon;
final List<Color> gradient;
final DateTime? startDate;
final DateTime? endDate;
final Function(DateTime? startDate, DateTime? endDate) onDateRangeChanged;
final VoidCallback onDownload;
final bool isLoading;
final int delay;
const _ReportOptionCard({
required this.title,
required this.subtitle,
required this.icon,
required this.gradient,
required this.startDate,
required this.endDate,
required this.onDateRangeChanged,
required this.onDownload,
required this.delay,
required this.isLoading,
});
@override
State<_ReportOptionCard> createState() => _ReportOptionCardState();
}
class _ReportOptionCardState extends State<_ReportOptionCard>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _slideAnimation;
bool _isExpanded = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_slideAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOutCubic,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggleExpanded() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_animationController.forward();
} else {
_animationController.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: widget.gradient,
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: widget.gradient.first.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
// Header Section
GestureDetector(
onTap: _toggleExpanded,
child: Container(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: Icon(widget.icon, color: AppColor.white, size: 32),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.title,
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
widget.subtitle,
style: AppStyle.sm.copyWith(
color: AppColor.white.withOpacity(0.8),
),
),
],
),
),
AnimatedRotation(
turns: _isExpanded ? 0.25 : 0,
duration: const Duration(milliseconds: 300),
child: Icon(
Icons.arrow_forward_ios,
color: AppColor.white.withOpacity(0.8),
size: 20,
),
),
],
),
),
),
// Expandable Content
SizeTransition(
sizeFactor: _slideAnimation,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.lang.select_date_range,
style: AppStyle.md.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
// Date Range Picker Field Style
DateRangePickerField(
placeholder: context.lang.select_date_range,
startDate: widget.startDate,
endDate: widget.endDate,
onChanged: widget.onDateRangeChanged,
),
const SizedBox(height: 20),
// Download Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed:
widget.startDate != null &&
widget.endDate != null
? widget.onDownload
: null,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.white,
foregroundColor: widget.gradient.first,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
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(
context.lang.download_report,
style: AppStyle.md.copyWith(
color: widget.gradient.first,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
),
],
),
),
),
],
),
);
}
}