201 lines
6.6 KiB
Dart
201 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:marco/controller/project_controller.dart';
|
|
import 'package:marco/controller/expense/expense_screen_controller.dart';
|
|
import 'package:marco/controller/permission_controller.dart';
|
|
import 'package:marco/helpers/widgets/my_text.dart';
|
|
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
|
|
import 'package:marco/model/expense/expense_list_model.dart';
|
|
import 'package:marco/model/expense/add_expense_bottom_sheet.dart';
|
|
import 'package:marco/view/expense/expense_filter_bottom_sheet.dart';
|
|
import 'package:marco/helpers/widgets/expense/expense_main_components.dart';
|
|
import 'package:marco/helpers/utils/permission_constants.dart';
|
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
|
|
|
|
|
class ExpenseMainScreen extends StatefulWidget {
|
|
const ExpenseMainScreen({super.key});
|
|
|
|
@override
|
|
State<ExpenseMainScreen> createState() => _ExpenseMainScreenState();
|
|
}
|
|
|
|
class _ExpenseMainScreenState extends State<ExpenseMainScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
final searchController = TextEditingController();
|
|
final expenseController = Get.put(ExpenseController());
|
|
final projectController = Get.find<ProjectController>();
|
|
final permissionController = Get.find<PermissionController>();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 2, vsync: this);
|
|
expenseController.fetchExpenses();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _refreshExpenses() async {
|
|
await expenseController.fetchExpenses();
|
|
}
|
|
|
|
void _openFilterBottomSheet() {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (_) => ExpenseFilterBottomSheet(
|
|
expenseController: expenseController,
|
|
scrollController: ScrollController(),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<ExpenseModel> _getFilteredExpenses({required bool isHistory}) {
|
|
final query = searchController.text.trim().toLowerCase();
|
|
final now = DateTime.now();
|
|
|
|
final filtered = expenseController.expenses.where((e) {
|
|
return query.isEmpty ||
|
|
e.expensesType.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) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
appBar: ExpenseAppBar(projectController: projectController),
|
|
body: Column(
|
|
children: [
|
|
// ---------------- TabBar ----------------
|
|
Container(
|
|
color: Colors.white,
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
labelColor: Colors.black,
|
|
unselectedLabelColor: Colors.grey,
|
|
indicatorColor: Colors.red,
|
|
tabs: const [
|
|
Tab(text: "Current Month"),
|
|
Tab(text: "History"),
|
|
],
|
|
),
|
|
),
|
|
|
|
// ---------------- Gray background for rest ----------------
|
|
Expanded(
|
|
child: Container(
|
|
color: Colors.grey[100], // Light gray background
|
|
child: Column(
|
|
children: [
|
|
// ---------------- Search ----------------
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
|
child: SearchAndFilter(
|
|
controller: searchController,
|
|
onChanged: (_) => setState(() {}),
|
|
onFilterTap: _openFilterBottomSheet,
|
|
expenseController: expenseController,
|
|
),
|
|
),
|
|
|
|
// ---------------- TabBarView ----------------
|
|
Expanded(
|
|
child: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildExpenseList(isHistory: false),
|
|
_buildExpenseList(isHistory: true),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
floatingActionButton:
|
|
permissionController.hasPermission(Permissions.expenseUpload)
|
|
? FloatingActionButton(
|
|
backgroundColor: Colors.red,
|
|
onPressed: showAddExpenseBottomSheet,
|
|
child: const Icon(Icons.add, color: Colors.white),
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
|
|
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<ScrollNotification>(
|
|
onNotification: (scrollInfo) {
|
|
if (scrollInfo.metrics.pixels ==
|
|
scrollInfo.metrics.maxScrollExtent &&
|
|
!expenseController.isLoading.value) {
|
|
expenseController.loadMoreExpenses();
|
|
}
|
|
return false;
|
|
},
|
|
child: ExpenseList(
|
|
expenseList: filteredList,
|
|
onViewDetail: () => expenseController.fetchExpenses(),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|