import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../components/spacer/spacer.dart'; import 'widgets/appbar.dart'; import 'widgets/summary_card.dart'; // Data Models class SalesData { final String dateFrom; final String dateTo; final SalesSummary summary; final List dailySales; SalesData({ required this.dateFrom, required this.dateTo, required this.summary, required this.dailySales, }); } class SalesSummary { final double totalSales; final int totalOrders; final int totalItems; final double averageOrderValue; final double totalTax; final double totalDiscount; final double netSales; SalesSummary({ required this.totalSales, required this.totalOrders, required this.totalItems, required this.averageOrderValue, required this.totalTax, required this.totalDiscount, required this.netSales, }); } class DailySales { final DateTime date; final double sales; final int orders; final int items; final double tax; final double discount; final double netSales; DailySales({ required this.date, required this.sales, required this.orders, required this.items, required this.tax, required this.discount, required this.netSales, }); } @RoutePage() class SalesPage extends StatefulWidget { const SalesPage({super.key}); @override State createState() => _SalesPageState(); } class _SalesPageState extends State with TickerProviderStateMixin { late AnimationController rotationAnimationController; late Animation rotationAnimation; late AnimationController slideAnimationController; late Animation slideAnimation; late AnimationController fadeAnimationController; late Animation fadeAnimation; @override void initState() { super.initState(); // Rotation Animation rotationAnimationController = AnimationController( duration: const Duration(seconds: 20), vsync: this, ); rotationAnimation = Tween( begin: 0, end: 6.28, // 2 * PI ).animate( CurvedAnimation( parent: rotationAnimationController, curve: Curves.linear, ), ); rotationAnimationController.repeat(); // 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() { rotationAnimationController.dispose(); slideAnimationController.dispose(); fadeAnimationController.dispose(); super.dispose(); } // Sample data based on your JSON final SalesData salesData = SalesData( dateFrom: "2025-08-01T00:00:00+07:00", dateTo: "2025-08-15T23:59:59.999999999+07:00", summary: SalesSummary( totalSales: 4291000, totalOrders: 62, totalItems: 62, averageOrderValue: 69209.67741935483, totalTax: 0, totalDiscount: 0, netSales: 4291000, ), dailySales: [ DailySales( date: DateTime.parse("2025-08-13T00:00:00Z"), sales: 3841000, orders: 52, items: 52, tax: 0, discount: 0, netSales: 3841000, ), DailySales( date: DateTime.parse("2025-08-14T00:00:00Z"), sales: 450000, orders: 10, items: 10, tax: 0, discount: 0, netSales: 450000, ), ], ); String formatCurrency(double amount) { return 'Rp ${amount.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}'; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, body: CustomScrollView( slivers: [ // App Bar SliverAppBar( expandedHeight: 120, floating: false, pinned: true, backgroundColor: AppColor.primary, flexibleSpace: SalesAppbar(rotationAnimation: rotationAnimation), ), // 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', formatCurrency( salesData.summary.totalSales, ), Icons.trending_up, AppColor.success, 0, ), ), SpaceWidth(12), Expanded( child: _buildSummaryCard( 'Total Orders', '${salesData.summary.totalOrders}', 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', formatCurrency( salesData.summary.averageOrderValue, ), Icons.attach_money, AppColor.warning, 200, ), ), SpaceWidth(12), Expanded( child: _buildSummaryCard( 'Total Items', '${salesData.summary.totalItems}', 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: salesData.summary.netSales, ), duration: const Duration( milliseconds: 2000, ), curve: Curves.easeOutCubic, builder: (context, countValue, child) { return Text( formatCurrency(countValue), 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) { final dailySale = salesData.dailySales[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(dailySale), ), ), ); }, childCount: salesData.dailySales.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(DailySales 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( formatCurrency(dailySale.sales), 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', formatCurrency(dailySale.tax), Icons.receipt, ), ), Expanded( child: _buildDetailItem( 'Discount', formatCurrency(dailySale.discount), 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, ), ); }, ), ], ), ); }, ); } }