Efril 0917c5132b
Some checks are pending
Build & Deploy iOS to TestFlight / build-and-deploy (push) Waiting to run
feat: update profit loss ui
2026-06-24 10:14:37 +07:00

146 lines
4.3 KiB
Dart

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart';
import '../../../common/theme/theme.dart';
import '../../../injection.dart';
import 'widgets/cost_breakdown.dart';
import 'widgets/profit_loss_header.dart';
import 'widgets/profit_loss_report.dart';
@RoutePage()
class FinancePage extends StatefulWidget implements AutoRouteWrapper {
const FinancePage({super.key});
@override
State<FinancePage> createState() => _FinancePageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (_) =>
getIt<ProfitLossLoaderBloc>()..add(ProfitLossLoaderEvent.fetched()),
child: this,
);
}
class _FinancePageState extends State<FinancePage>
with SingleTickerProviderStateMixin {
late AnimationController _fadeController;
late Animation<double> _fadeAnimation;
int _selectedTabIndex = 0;
@override
void initState() {
super.initState();
_fadeController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn));
_fadeController.forward();
}
@override
void dispose() {
_fadeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: BlocListener<ProfitLossLoaderBloc, ProfitLossLoaderState>(
listenWhen: (previous, current) =>
previous.dateFrom != current.dateFrom ||
previous.dateTo != current.dateTo,
listener: (context, state) {
context.read<ProfitLossLoaderBloc>().add(
ProfitLossLoaderEvent.fetched(),
);
},
child: BlocBuilder<ProfitLossLoaderBloc, ProfitLossLoaderState>(
builder: (context, state) {
return CustomScrollView(
slivers: [
// Header with gradient background, tabs, and summary
SliverToBoxAdapter(
child: FadeTransition(
opacity: _fadeAnimation,
child: ProfitLossHeader(
state: state,
selectedTabIndex: _selectedTabIndex,
onTabChanged: (index) {
setState(() {
_selectedTabIndex = index;
});
_onTabChanged(context, index);
},
),
),
),
// Profit Loss Report Table
SliverToBoxAdapter(
child: FadeTransition(
opacity: _fadeAnimation,
child: ProfitLossReport(
mainSummary: state.profitLoss.mainSummary,
summary: state.profitLoss.summary,
selectedTabIndex: _selectedTabIndex,
),
),
),
// Cost Breakdown
SliverToBoxAdapter(
child: FadeTransition(
opacity: _fadeAnimation,
child: CostBreakdown(
purchasing: state.profitLoss.purchasing,
selectedTabIndex: _selectedTabIndex,
dateFrom: state.dateFrom,
dateTo: state.dateTo,
),
),
),
// Bottom spacing
const SliverToBoxAdapter(child: SizedBox(height: 100)),
],
);
},
),
),
);
}
void _onTabChanged(BuildContext context, int index) {
final now = DateTime.now();
DateTime dateFrom;
DateTime dateTo;
if (index == 0) {
// Today
dateFrom = DateTime(now.year, now.month, now.day);
dateTo = DateTime(now.year, now.month, now.day, 23, 59, 59);
} else {
// MTD (Month-to-Date)
dateFrom = DateTime(now.year, now.month, 1);
dateTo = now;
}
context.read<ProfitLossLoaderBloc>().add(
ProfitLossLoaderEvent.rangeDateChanged(dateFrom, dateTo),
);
}
}