import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:line_icons/line_icons.dart'; import '../../../application/analytic/category_analytic_loader/category_analytic_loader_bloc.dart'; import '../../../application/analytic/profit_loss_loader/profit_loss_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 'widgets/cash_flow.dart'; import 'widgets/category.dart'; import 'widgets/product.dart'; import 'widgets/profit_loss.dart'; import 'widgets/summary_card.dart'; @RoutePage() class FinancePage extends StatefulWidget implements AutoRouteWrapper { const FinancePage({super.key}); @override State createState() => _FinancePageState(); @override Widget wrappedRoute(BuildContext context) => MultiBlocProvider( providers: [ BlocProvider( create: (_) => getIt()..add(ProfitLossLoaderEvent.fetched()), ), BlocProvider( create: (context) => getIt() ..add(CategoryAnalyticLoaderEvent.fetched()), ), ], child: this, ); } class _FinancePageState extends State with TickerProviderStateMixin { late AnimationController _slideController; late AnimationController _fadeController; late AnimationController _scaleController; late Animation _slideAnimation; late Animation _fadeAnimation; late Animation _scaleAnimation; String selectedPeriod = 'Hari ini'; final List periods = [ 'Hari ini', 'Minggu ini', 'Bulan ini', 'Tahun ini', ]; @override void initState() { super.initState(); _slideController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _fadeController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _scaleController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), ); // Start animations _fadeController.forward(); Future.delayed(const Duration(milliseconds: 200), () { _slideController.forward(); }); Future.delayed(const Duration(milliseconds: 400), () { _scaleController.forward(); }); } @override void dispose() { _slideController.dispose(); _fadeController.dispose(); _scaleController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, body: BlocBuilder( builder: (context, state) { return CustomScrollView( slivers: [ // SliverAppBar with animated background SliverAppBar( expandedHeight: 120, floating: false, pinned: true, backgroundColor: AppColor.primary, elevation: 0, flexibleSpace: CustomAppBar(title: 'Keuangan'), ), // Header dengan filter periode SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: _buildPeriodSelector(), ), ), // Summary Cards SliverToBoxAdapter( child: SlideTransition( position: _slideAnimation, child: _buildSummaryCards(state.profitLoss.summary), ), ), // Cash Flow Analysis SliverToBoxAdapter( child: ScaleTransition( scale: _scaleAnimation, child: FinanceCashFlow(dailyData: state.profitLoss.data), ), ), // Profit Loss Detail SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: FinanceProfitLoss(data: state.profitLoss.summary), ), ), BlocBuilder< CategoryAnalyticLoaderBloc, CategoryAnalyticLoaderState >( builder: (context, stateCategory) { return SliverToBoxAdapter( child: SlideTransition( position: _slideAnimation, child: FinanceCategory( categories: stateCategory.categoryAnalytic.data, ), ), ); }, ), // Product Analysis Section SliverToBoxAdapter( child: SlideTransition( position: _slideAnimation, child: _buildProductAnalysis(state.profitLoss.productData), ), ), // Transaction Categories // Bottom spacing const SliverToBoxAdapter(child: SizedBox(height: 100)), ], ); }, ), ); } Widget _buildPeriodSelector() { return Container( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: AppColor.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColor.border), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: selectedPeriod, isExpanded: true, icon: const Icon( LineIcons.angleDown, color: AppColor.primary, ), style: AppStyle.md, items: periods.map((String period) { return DropdownMenuItem( value: period, child: Text(period), ); }).toList(), onChanged: (String? newValue) { setState(() { selectedPeriod = newValue!; }); }, ), ), ), ), const SizedBox(width: 12), Container( decoration: BoxDecoration( color: AppColor.primary, borderRadius: BorderRadius.circular(12), ), child: IconButton( onPressed: () {}, icon: const Icon(LineIcons.calendar, color: AppColor.white), ), ), ], ), ); } Widget _buildSummaryCards(ProfitLossSummary summary) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ Row( children: [ Expanded( child: FinanceSummaryCard( title: 'Total Pendapatan', amount: summary.totalRevenue.currencyFormatRp, icon: LineIcons.arrowUp, color: AppColor.success, isPositive: true, ), ), const SizedBox(width: 12), Expanded( child: FinanceSummaryCard( title: 'Total Pengeluaran', amount: summary.totalCost.currencyFormatRp, icon: LineIcons.arrowDown, color: AppColor.error, isPositive: false, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: FinanceSummaryCard( title: 'Keuntungan Bersih', amount: summary.netProfit.currencyFormatRp, icon: LineIcons.lineChart, color: AppColor.info, isPositive: true, ), ), const SizedBox(width: 12), Expanded( child: FinanceSummaryCard( title: 'Margin Profit', amount: '${summary.profitabilityRatio.round()}%', icon: LineIcons.percent, color: AppColor.warning, isPositive: true, ), ), ], ), ], ), ); } Widget _buildProductAnalysis(List products) { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColor.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColor.textLight.withOpacity(0.1), spreadRadius: 1, blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColor.info.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon( LineIcons.shoppingBag, color: AppColor.info, size: 20, ), ), const SizedBox(width: 12), Text( 'Analisis Produk', style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), ), const Spacer(), TextButton( onPressed: () {}, child: Text( 'Lihat Semua', style: AppStyle.sm.copyWith(color: AppColor.primary), ), ), ], ), // Product list ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.only(top: 12), itemCount: products.length, separatorBuilder: (context, index) => const SizedBox(height: 12), itemBuilder: (context, index) { final product = products[index]; return ProfitLossProduct(product: product); }, ), ], ), ); } }