import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/controller/expense/expense_screen_controller.dart'; import 'package:on_field_work/controller/permission_controller.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; import 'package:on_field_work/model/expense/expense_list_model.dart'; import 'package:on_field_work/model/expense/add_expense_bottom_sheet.dart'; import 'package:on_field_work/view/expense/expense_filter_bottom_sheet.dart'; import 'package:on_field_work/helpers/widgets/expense/expense_main_components.dart'; import 'package:on_field_work/helpers/utils/permission_constants.dart'; import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; class ExpenseMainScreen extends StatefulWidget { const ExpenseMainScreen({super.key}); @override State createState() => _ExpenseMainScreenState(); } class _ExpenseMainScreenState extends State with SingleTickerProviderStateMixin, UIMixin { late TabController _tabController; final searchController = TextEditingController(); final expenseController = Get.put(ExpenseController()); final projectController = Get.find(); final permissionController = Get.put(PermissionController()); @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); // Defer expense fetch to after first build WidgetsBinding.instance.addPostFrameCallback((_) { expenseController.fetchExpenses(); }); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _refreshExpenses() async { await expenseController.fetchExpenses(); } void _openFilterBottomSheet() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => ExpenseFilterBottomSheet( expenseController: expenseController, scrollController: ScrollController(), ), ); } List _getFilteredExpenses({required bool isHistory}) { final query = searchController.text.trim().toLowerCase(); final now = DateTime.now(); final filtered = expenseController.expenses.where((e) { return query.isEmpty || e.expenseCategory.name.toLowerCase().contains(query) || e.supplerName.toLowerCase().contains(query) || e.paymentMode.name.toLowerCase().contains(query); }).toList() ..sort((a, b) => b.transactionDate.compareTo(a.transactionDate)); return isHistory ? filtered .where((e) => e.transactionDate.isBefore(DateTime(now.year, now.month))) .toList() : filtered .where((e) => e.transactionDate.month == now.month && e.transactionDate.year == now.year) .toList(); } @override Widget build(BuildContext context) { final Color appBarColor = contentTheme.primary; return Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar( title: "Expense & Reimbursement", backgroundColor: appBarColor, onBackPressed: () => Get.toNamed('/dashboard/finance'), ), body: Stack( children: [ // === FULL GRADIENT BEHIND APPBAR & TABBAR === Positioned.fill( child: Column( children: [ Container( height: 80, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ appBarColor, appBarColor.withOpacity(0.0), ], ), ), ), Expanded( child: Container(color: Colors.grey[100]), ), ], ), ), // === MAIN CONTENT === SafeArea( top: false, bottom: true, child: Column( children: [ // TAB BAR WITH TRANSPARENT BACKGROUND Container( decoration: const BoxDecoration(color: Colors.transparent), child: TabBar( controller: _tabController, labelColor: Colors.white, unselectedLabelColor: Colors.white70, indicatorColor: Colors.white, indicatorWeight: 3, tabs: const [ Tab(text: "Current Month"), Tab(text: "History"), ], ), ), // CONTENT AREA Expanded( child: Container( color: Colors.transparent, child: Column( children: [ // SEARCH & FILTER Padding( padding: const EdgeInsets.symmetric(horizontal: 0), child: SearchAndFilter( controller: searchController, onChanged: (_) => setState(() {}), onFilterTap: _openFilterBottomSheet, expenseController: expenseController, ), ), // TABBAR VIEW Expanded( child: TabBarView( controller: _tabController, children: [ _buildExpenseList(isHistory: false), _buildExpenseList(isHistory: true), ], ), ), ], ), ), ), ], ), ), ], ), floatingActionButton: Obx(() { if (permissionController.permissions.isEmpty) return const SizedBox.shrink(); final canUpload = permissionController.hasPermission(Permissions.expenseUpload); return canUpload ? FloatingActionButton.extended( backgroundColor: contentTheme.primary, onPressed: showAddExpenseBottomSheet, icon: const Icon(Icons.add, color: Colors.white), label: const Text( "Add Expense", style: TextStyle(color: Colors.white, fontSize: 16), ), ) : const SizedBox.shrink(); }), ); } Widget _buildExpenseList({required bool isHistory}) { return Obx(() { if (expenseController.isLoading.value && expenseController.expenses.isEmpty) { return SkeletonLoaders.expenseListSkeletonLoader(); } final filteredList = _getFilteredExpenses(isHistory: isHistory); return MyRefreshIndicator( onRefresh: _refreshExpenses, child: filteredList.isEmpty ? ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ SizedBox( height: MediaQuery.of(context).size.height * 0.5, child: Center( child: MyText.bodyMedium( expenseController.errorMessage.isNotEmpty ? expenseController.errorMessage.value : "No expenses found", color: expenseController.errorMessage.isNotEmpty ? Colors.red : Colors.grey, ), ), ), ], ) : NotificationListener( onNotification: (scrollInfo) { if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !expenseController.isLoading.value) { expenseController.loadMoreExpenses(); } return false; }, child: ExpenseList( expenseList: filteredList, onViewDetail: () => expenseController.fetchExpenses(), ), ), ); }); } }