From bfd460489781b07c625a45a1d813cd845ef84227 Mon Sep 17 00:00:00 2001 From: efrilm Date: Tue, 19 Aug 2025 13:09:00 +0700 Subject: [PATCH] feat: update download report --- .../pages/download/download_report_page.dart | 587 +++++++----------- 1 file changed, 230 insertions(+), 357 deletions(-) diff --git a/lib/presentation/pages/download/download_report_page.dart b/lib/presentation/pages/download/download_report_page.dart index b09e6c7..08c8511 100644 --- a/lib/presentation/pages/download/download_report_page.dart +++ b/lib/presentation/pages/download/download_report_page.dart @@ -3,6 +3,7 @@ import 'package:auto_route/auto_route.dart'; import '../../../common/theme/theme.dart'; import '../../components/appbar/appbar.dart'; +import '../../components/field/date_range_picker_field.dart'; @RoutePage() class DownloadReportPage extends StatefulWidget { @@ -20,6 +21,16 @@ class _DownloadReportPageState extends State late Animation _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(); @@ -59,28 +70,26 @@ class _DownloadReportPageState extends State super.dispose(); } - void _showDateRangePicker(String reportType) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => _DateRangeBottomSheet( - reportType: reportType, - onDateRangeSelected: (dateRange) { - setState(() {}); - // Handle download logic here - _downloadReport(reportType, dateRange); - }, - ), - ); - } + void _downloadReport( + String reportType, + DateTime? startDate, + 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, + ), + ); + return; + } - void _downloadReport(String reportType, DateTimeRange dateRange) { // Implement download logic here ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - 'Downloading $reportType from ${_formatDate(dateRange.start)} to ${_formatDate(dateRange.end)}', + 'Downloading $reportType from ${_formatDate(startDate)} to ${_formatDate(endDate)}', ), backgroundColor: AppColor.success, ), @@ -128,8 +137,19 @@ class _DownloadReportPageState extends State AppColor.primary, AppColor.primaryLight, ], - onTap: () => - _showDateRangePicker('Transaction Report'), + startDate: _transactionStartDate, + endDate: _transactionEndDate, + onDateRangeChanged: (start, end) { + setState(() { + _transactionStartDate = start; + _transactionEndDate = end; + }); + }, + onDownload: () => _downloadReport( + 'Transaction Report', + _transactionStartDate, + _transactionEndDate, + ), delay: 200, ), @@ -145,31 +165,68 @@ class _DownloadReportPageState extends State AppColor.secondary, AppColor.secondaryLight, ], - onTap: () => _showDateRangePicker('Inventory Report'), + startDate: _inventoryStartDate, + endDate: _inventoryEndDate, + onDateRangeChanged: (start, end) { + setState(() { + _inventoryStartDate = start; + _inventoryEndDate = end; + }); + }, + onDownload: () => _downloadReport( + 'Inventory Report', + _inventoryStartDate, + _inventoryEndDate, + ), delay: 400, ), const SizedBox(height: 20), - // Additional Report Options + // 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)], - onTap: () => _showDateRangePicker('Sales Report'), + 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)], - onTap: () => _showDateRangePicker('Customer Report'), + startDate: _customerStartDate, + endDate: _customerEndDate, + onDateRangeChanged: (start, end) { + setState(() { + _customerStartDate = start; + _customerEndDate = end; + }); + }, + onDownload: () => _downloadReport( + 'Customer Report', + _customerStartDate, + _customerEndDate, + ), delay: 800, ), ], @@ -192,7 +249,10 @@ class _ReportOptionCard extends StatefulWidget { final String subtitle; final IconData icon; final List gradient; - final VoidCallback onTap; + final DateTime? startDate; + final DateTime? endDate; + final Function(DateTime? startDate, DateTime? endDate) onDateRangeChanged; + final VoidCallback onDownload; final int delay; const _ReportOptionCard({ @@ -200,7 +260,10 @@ class _ReportOptionCard extends StatefulWidget { required this.subtitle, required this.icon, required this.gradient, - required this.onTap, + required this.startDate, + required this.endDate, + required this.onDateRangeChanged, + required this.onDownload, required this.delay, }); @@ -211,18 +274,19 @@ class _ReportOptionCard extends StatefulWidget { class _ReportOptionCardState extends State<_ReportOptionCard> with SingleTickerProviderStateMixin { late AnimationController _animationController; - late Animation _scaleAnimation; - bool _isPressed = false; + late Animation _slideAnimation; + bool _isExpanded = false; @override void initState() { super.initState(); _animationController = AnimationController( - duration: const Duration(milliseconds: 150), + duration: const Duration(milliseconds: 300), vsync: this, ); - _scaleAnimation = Tween(begin: 1.0, end: 0.95).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + _slideAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOutCubic, ); } @@ -232,359 +296,168 @@ class _ReportOptionCardState extends State<_ReportOptionCard> super.dispose(); } - @override - Widget build(BuildContext context) { - return GestureDetector( - onTapDown: (_) { - setState(() => _isPressed = true); + void _toggleExpanded() { + setState(() { + _isExpanded = !_isExpanded; + if (_isExpanded) { _animationController.forward(); - }, - onTapUp: (_) { - setState(() => _isPressed = false); + } else { _animationController.reverse(); - widget.onTap(); - }, - onTapCancel: () { - setState(() => _isPressed = false); - _animationController.reverse(); - }, - child: ScaleTransition( - scale: _scaleAnimation, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: _isPressed - ? widget.gradient.map((c) => c.withOpacity(0.8)).toList() - : widget.gradient, - ), - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: widget.gradient.first.withOpacity(0.3), - blurRadius: _isPressed ? 8 : 15, - offset: Offset(0, _isPressed ? 2 : 8), - ), - ], - ), - 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), - ), - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: AppColor.white.withOpacity(0.8), - size: 20, - ), - ], - ), - ), - ), - ); - } -} - -class _DateRangeBottomSheet extends StatefulWidget { - final String reportType; - final Function(DateTimeRange) onDateRangeSelected; - - const _DateRangeBottomSheet({ - required this.reportType, - required this.onDateRangeSelected, - }); - - @override - State<_DateRangeBottomSheet> createState() => _DateRangeBottomSheetState(); -} - -class _DateRangeBottomSheetState extends State<_DateRangeBottomSheet> - with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _animation; - - DateTime? _startDate; - DateTime? _endDate; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _animation = CurvedAnimation( - parent: _controller, - curve: Curves.easeOutCubic, - ); - _controller.forward(); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - void _selectStartDate() async { - final date = await showDatePicker( - context: context, - initialDate: _startDate ?? DateTime.now(), - firstDate: DateTime(2020), - lastDate: DateTime.now(), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light(primary: AppColor.primary), - ), - child: child!, - ); - }, - ); - if (date != null) { - setState(() => _startDate = date); - } - } - - void _selectEndDate() async { - final date = await showDatePicker( - context: context, - initialDate: _endDate ?? DateTime.now(), - firstDate: _startDate ?? DateTime(2020), - lastDate: DateTime.now(), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light(primary: AppColor.primary), - ), - child: child!, - ); - }, - ); - if (date != null) { - setState(() => _endDate = date); - } - } - - void _downloadReport() { - if (_startDate != null && _endDate != null) { - final dateRange = DateTimeRange(start: _startDate!, end: _endDate!); - widget.onDateRangeSelected(dateRange); - Navigator.pop(context); - } - } - - String _formatDate(DateTime? date) { - if (date == null) return 'Select Date'; - return '${date.day}/${date.month}/${date.year}'; + } + }); } @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _animation, - builder: (context, child) { - return Transform.translate( - offset: Offset(0, (1 - _animation.value) * 300), - child: Container( - decoration: const BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - ), - ), - child: Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 20, - bottom: MediaQuery.of(context).viewInsets.bottom + 20, - ), - child: Column( - mainAxisSize: MainAxisSize.min, + 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: [ - // Handle bar Container( - width: 50, - height: 4, + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.border, - borderRadius: BorderRadius.circular(2), + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), ), + child: Icon(widget.icon, color: AppColor.white, size: 32), ), - - const SizedBox(height: 20), - - // Title - Text( - widget.reportType, - style: AppStyle.h6.copyWith(fontWeight: FontWeight.bold), - ), - - const SizedBox(height: 8), - - Text( - 'Select date range for your report', - style: AppStyle.md.copyWith(color: AppColor.textSecondary), - ), - - const SizedBox(height: 30), - - // Date Selection - Row( - children: [ - Expanded( - child: _DateSelectionCard( - title: 'Start Date', - date: _formatDate(_startDate), - onTap: _selectStartDate, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _DateSelectionCard( - title: 'End Date', - date: _formatDate(_endDate), - onTap: _selectEndDate, - ), - ), - ], - ), - - const SizedBox(height: 30), - - // Download Button - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _startDate != null && _endDate != null - ? _downloadReport - : null, - style: ElevatedButton.styleFrom( - backgroundColor: AppColor.primary, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.download_rounded, + 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(width: 8), - Text( - 'Download Report', - 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, ), ), - - const SizedBox(height: 10), ], ), ), ), - ); - }, - ); - } -} -class _DateSelectionCard extends StatelessWidget { - final String title; - final String date; - final VoidCallback onTap; + // 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( + 'Select Date Range', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), - const _DateSelectionCard({ - required this.title, - required this.date, - required this.onTap, - }); + // Date Range Picker Field Style + DateRangePickerField( + placeholder: 'Select date range', + startDate: widget.startDate, + endDate: widget.endDate, + onChanged: widget.onDateRangeChanged, + ), - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.background, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.border), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - child: Text( - date, - style: AppStyle.md.copyWith( - fontWeight: FontWeight.w600, - color: date == 'Select Date' - ? AppColor.textLight - : AppColor.textPrimary, + 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: 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, + ), + ), + ], + ), + ), + ), + ], ), ), - ), - Icon( - Icons.calendar_today_outlined, - color: AppColor.primary, - size: 18, - ), - ], + ], + ), ), - ], - ), + ), + ], ), ); }