marco.pms.mobileapp/lib/view/expense/expense_screen.dart
Vaibhav Surve 311002f3ba Refactor expense deletion confirmation dialog and add validation to add expense form
- 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.
2025-08-26 11:09:50 +05:30

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,
);
}
}