import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../application/sales/sales_loader/sales_loader_bloc.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/analytic/analytic.dart'; import '../../../injection.dart'; import '../../components/appbar/appbar.dart'; import '../../components/spacer/spacer.dart'; import 'widgets/summary_card.dart'; @RoutePage() class SalesPage extends StatefulWidget implements AutoRouteWrapper { const SalesPage({super.key}); @override State createState() => _SalesPageState(); @override Widget wrappedRoute(BuildContext context) => BlocProvider( create: (context) => getIt()..add(SalesLoaderEvent.fectched()), child: this, ); } class _SalesPageState extends State with TickerProviderStateMixin { late AnimationController slideAnimationController; late Animation slideAnimation; late AnimationController fadeAnimationController; late Animation fadeAnimation; @override void initState() { super.initState(); // Slide Animation slideAnimationController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation( parent: slideAnimationController, curve: Curves.easeOutCubic, ), ); // Fade Animation fadeAnimationController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: fadeAnimationController, curve: Curves.easeOut), ); // Start animations Future.delayed(const Duration(milliseconds: 300), () { slideAnimationController.forward(); fadeAnimationController.forward(); }); } @override void dispose() { slideAnimationController.dispose(); fadeAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, body: BlocBuilder( builder: (context, state) { return CustomScrollView( slivers: [ // App Bar SliverAppBar( expandedHeight: 120, floating: false, pinned: true, backgroundColor: AppColor.primary, flexibleSpace: CustomAppBar(title: 'Penjualan'), ), // Date Range Header SliverToBoxAdapter( child: SlideTransition( position: slideAnimation, child: FadeTransition( opacity: fadeAnimation, child: Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), decoration: BoxDecoration( color: AppColor.surface, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Icon( Icons.date_range, color: AppColor.primary, size: 20, ), SpaceWidth(8), Text( 'Aug 1 - Aug 15, 2025', style: AppStyle.md.copyWith( color: AppColor.textPrimary, fontWeight: FontWeight.w500, ), ), ], ), ), ), ), ), // Summary Cards SliverToBoxAdapter( child: SlideTransition( position: slideAnimation, child: FadeTransition( opacity: fadeAnimation, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Summary', style: AppStyle.xxl.copyWith( fontWeight: FontWeight.bold, color: AppColor.textPrimary, ), ), const SpaceHeight(16), TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 800), curve: Curves.elasticOut, builder: (context, value, child) { return Transform.scale( scale: value, child: Row( children: [ Expanded( child: _buildSummaryCard( 'Total Sales', state .sales .summary .totalSales .currencyFormatRp, Icons.trending_up, AppColor.success, 0, ), ), SpaceWidth(12), Expanded( child: _buildSummaryCard( 'Total Orders', state.sales.summary.totalOrders .toString(), Icons.shopping_cart, AppColor.info, 100, ), ), ], ), ); }, ), const SpaceHeight(12), TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 1000), curve: Curves.elasticOut, builder: (context, value, child) { return Transform.scale( scale: value, child: Row( children: [ Expanded( child: _buildSummaryCard( 'Avg Order Value', state.sales.summary.averageOrderValue .round() .currencyFormatRp, Icons.attach_money, AppColor.warning, 200, ), ), SpaceWidth(12), Expanded( child: _buildSummaryCard( 'Total Items', state.sales.summary.totalItems .toString(), Icons.inventory, AppColor.primary, 300, ), ), ], ), ); }, ), ], ), ), ), ), ), // Net Sales Card SliverToBoxAdapter( child: SlideTransition( position: slideAnimation, child: FadeTransition( opacity: fadeAnimation, child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 1200), curve: Curves.bounceOut, builder: (context, value, child) { return Transform.scale( scale: value, child: Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: const LinearGradient( colors: AppColor.successGradient, begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColor.success.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 5), ), ], ), child: Row( children: [ TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 1500), curve: Curves.elasticOut, builder: (context, iconValue, child) { return Transform.rotate( angle: iconValue * 0.1, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular( 12, ), ), child: const Icon( Icons.account_balance_wallet, color: AppColor.textWhite, size: 28, ), ), ); }, ), SpaceWidth(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Net Sales', style: TextStyle( color: AppColor.textWhite.withOpacity( 0.9, ), fontSize: 14, fontWeight: FontWeight.w500, ), ), const SpaceHeight(4), TweenAnimationBuilder( tween: Tween( begin: 0.0, end: state.sales.summary.netSales .toDouble(), ), duration: const Duration( milliseconds: 2000, ), curve: Curves.easeOutCubic, builder: (context, countValue, child) { return Text( state .sales .summary .netSales .currencyFormatRp, style: const TextStyle( color: AppColor.textWhite, fontSize: 24, fontWeight: FontWeight.bold, ), ); }, ), ], ), ), ], ), ), ); }, ), ), ), ), // Daily Sales Section Header SliverToBoxAdapter( child: SlideTransition( position: slideAnimation, child: FadeTransition( opacity: fadeAnimation, child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), child: Text( 'Daily Breakdown', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColor.textPrimary, ), ), ), ), ), ), // Daily Sales List SliverList( delegate: SliverChildBuilderDelegate((context, index) { return SlideTransition( position: Tween( begin: Offset(index.isEven ? -1.0 : 1.0, 0), end: Offset.zero, ).animate( CurvedAnimation( parent: slideAnimationController, curve: Interval( 0.2 + (index * 0.1), 0.8 + (index * 0.1), curve: Curves.easeOutBack, ), ), ), child: FadeTransition( opacity: Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: fadeAnimationController, curve: Interval( 0.3 + (index * 0.1), 0.9 + (index * 0.1), curve: Curves.easeOut, ), ), ), child: Container( margin: const EdgeInsets.symmetric( horizontal: 16, vertical: 6, ), decoration: BoxDecoration( color: AppColor.surface, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: _buildDailySalesItem(state.sales.data[index]), ), ), ); }, childCount: state.sales.data.length), ), // Bottom Padding const SliverToBoxAdapter(child: SpaceHeight(32)), ], ); }, ), ); } Widget _buildSummaryCard( String title, String value, IconData icon, Color color, int delay, ) { return SalesSummaryCard( fadeAnimation: fadeAnimation, title: title, value: value, icon: icon, color: color, delay: delay, ); } Widget _buildDailySalesItem(SalesAnalyticData dailySale) { return ExpansionTile( leading: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColor.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.calendar_today, color: AppColor.primary, size: 20), ), title: Text( '${dailySale.date.day}/${dailySale.date.month}/${dailySale.date.year}', style: const TextStyle( fontWeight: FontWeight.bold, color: AppColor.textPrimary, ), ), subtitle: Text( dailySale.sales.currencyFormatRp, style: TextStyle( color: AppColor.success, fontWeight: FontWeight.w600, fontSize: 16, ), ), trailing: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AppColor.info.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( '${dailySale.orders} orders', style: TextStyle( color: AppColor.info, fontWeight: FontWeight.w500, fontSize: 12, ), ), ), children: [ Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: _buildDetailItem( 'Items', '${dailySale.items}', Icons.inventory_2, ), ), Expanded( child: _buildDetailItem( 'Tax', dailySale.tax.currencyFormatRp, Icons.receipt, ), ), Expanded( child: _buildDetailItem( 'Discount', dailySale.discount.currencyFormatRp, Icons.local_offer, ), ), ], ), ), ], ); } Widget _buildDetailItem(String label, String value, IconData icon) { return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 600), curve: Curves.bounceOut, builder: (context, animValue, child) { return Transform.scale( scale: animValue, child: Column( children: [ TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 800), curve: Curves.elasticOut, builder: (context, iconValue, child) { return Transform.rotate( angle: iconValue * 0.1, child: Icon(icon, color: AppColor.textSecondary, size: 20), ); }, ), const SpaceHeight(4), Text( label, style: TextStyle(color: AppColor.textSecondary, fontSize: 12), ), const SpaceHeight(2), AnimatedBuilder( animation: fadeAnimation, builder: (context, child) { return Text( value, style: TextStyle( color: AppColor.textPrimary, fontWeight: FontWeight.w600, fontSize: 14, ), ); }, ), ], ), ); }, ); } }