- Replaced the custom delete confirmation dialog with a reusable ConfirmDialog widget for better code organization and reusability. - Improved the add expense bottom sheet by implementing form validation using a GlobalKey and TextFormField. - Enhanced user experience by adding validation for required fields and specific formats (e.g., GST, transaction ID). - Updated the expense list to reflect changes in the confirmation dialog and improved the handling of attachments. - Cleaned up code by removing unnecessary comments and ensuring consistent formatting.
161 lines
5.9 KiB
Dart
161 lines
5.9 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_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> {
|
|
bool isHistoryView = false;
|
|
final searchController = TextEditingController();
|
|
final expenseController = Get.put(ExpenseController());
|
|
final projectController = Get.find<ProjectController>();
|
|
final permissionController = Get.find<PermissionController>();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
expenseController.fetchExpenses();
|
|
}
|
|
|
|
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() {
|
|
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 isHistoryView
|
|
? 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: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
SearchAndFilter(
|
|
controller: searchController,
|
|
onChanged: (_) => setState(() {}),
|
|
onFilterTap: _openFilterBottomSheet,
|
|
expenseController: expenseController,
|
|
),
|
|
ToggleButtonsRow(
|
|
isHistoryView: isHistoryView,
|
|
onToggle: (v) => setState(() => isHistoryView = v),
|
|
),
|
|
Expanded(
|
|
child: Obx(() {
|
|
// Loader while fetching first time
|
|
if (expenseController.isLoading.value &&
|
|
expenseController.expenses.isEmpty) {
|
|
return SkeletonLoaders.expenseListSkeletonLoader();
|
|
}
|
|
|
|
final filteredList = _getFilteredExpenses();
|
|
|
|
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(),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
// ✅ FAB only if user has expenseUpload permission
|
|
floatingActionButton:
|
|
permissionController.hasPermission(Permissions.expenseUpload)
|
|
? FloatingActionButton(
|
|
backgroundColor: Colors.red,
|
|
onPressed: showAddExpenseBottomSheet,
|
|
child: const Icon(Icons.add, color: Colors.white),
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
}
|