feat: Enhance ExpenseByStatusWidget with navigation and filter functionality; update ExpenseMainScreen to fetch expenses after UI initialization

This commit is contained in:
Vaibhav Surve 2025-10-31 17:13:43 +05:30
parent 1e39210a29
commit d62f0d2c60
2 changed files with 153 additions and 41 deletions

View File

@ -4,6 +4,9 @@ import 'package:marco/controller/dashboard/dashboard_controller.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/utils/utils.dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
import 'package:marco/controller/expense/expense_screen_controller.dart';
import 'package:marco/view/expense/expense_screen.dart';
import 'package:collection/collection.dart';
class ExpenseByStatusWidget extends StatelessWidget {
final DashboardController controller;
@ -16,34 +19,106 @@ class ExpenseByStatusWidget extends StatelessWidget {
required String title,
required String amount,
required String count,
required VoidCallback onTap,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
CircleAvatar(
backgroundColor: color.withOpacity(0.15),
radius: 22,
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(title, fontWeight: 600),
const SizedBox(height: 2),
MyText.titleMedium(amount, color: Colors.blue, fontWeight: 700),
],
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
CircleAvatar(
backgroundColor: color.withOpacity(0.15),
radius: 22,
child: Icon(icon, color: color, size: 24),
),
),
MyText.titleMedium(count, color: Colors.blue, fontWeight: 700),
const Icon(Icons.chevron_right, color: Colors.blue, size: 24),
],
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(title, fontWeight: 600),
const SizedBox(height: 2),
MyText.titleMedium(amount,
color: Colors.blue, fontWeight: 700),
],
),
),
MyText.titleMedium(count, color: Colors.blue, fontWeight: 700),
const Icon(Icons.chevron_right, color: Colors.blue, size: 24),
],
),
),
);
}
// Navigate with status filter
Future<void> _navigateToExpenseWithFilter(
BuildContext context, String statusName) async {
final expenseController = Get.put(ExpenseController());
// 1 Ensure global projects and master data are loaded
if (expenseController.projectsMap.isEmpty) {
await expenseController.fetchGlobalProjects();
}
if (expenseController.expenseStatuses.isEmpty) {
await expenseController.fetchMasterData();
}
// 2 Auto-select current project from DashboardController
final dashboardController = Get.find<DashboardController>();
final currentProjectId =
dashboardController.projectController.selectedProjectId.value;
final projectName = expenseController.projectsMap.entries
.firstWhereOrNull((entry) => entry.value == currentProjectId)
?.key;
expenseController.selectedProject.value = projectName ?? '';
// 3 Select status filter
final matchedStatus = expenseController.expenseStatuses.firstWhereOrNull(
(e) => e.name.toLowerCase() == statusName.toLowerCase(),
);
expenseController.selectedStatus.value = matchedStatus?.id ?? '';
// 4 Fetch expenses immediately with applied filters
await expenseController.fetchExpenses();
// 5 Navigate to Expense screen
Get.to(() => const ExpenseMainScreen());
}
// Navigate without status filter
Future<void> _navigateToExpenseWithoutFilter() async {
final expenseController = Get.put(ExpenseController());
// Ensure global projects loaded
if (expenseController.projectsMap.isEmpty) {
await expenseController.fetchGlobalProjects();
}
// Auto-select current project
final dashboardController = Get.find<DashboardController>();
final currentProjectId =
dashboardController.projectController.selectedProjectId.value;
final projectName = expenseController.projectsMap.entries
.firstWhereOrNull((entry) => entry.value == currentProjectId)
?.key;
expenseController.selectedProject.value = projectName ?? '';
expenseController.selectedStatus.value = '';
// Fetch expenses with project filter (no status)
await expenseController.fetchExpenses();
// Navigate to Expense screen
Get.to(() => const ExpenseMainScreen());
}
@override
Widget build(BuildContext context) {
return Obx(() {
@ -78,12 +153,17 @@ class ExpenseByStatusWidget extends StatelessWidget {
children: [
MyText.titleMedium("Expense - By Status", fontWeight: 700),
const SizedBox(height: 16),
// Status tiles
_buildStatusTile(
icon: Icons.currency_rupee,
color: Colors.blue,
title: "Pending Payment",
amount: Utils.formatCurrency(data.processPending.totalAmount),
count: data.processPending.count.toString(),
onTap: () {
_navigateToExpenseWithFilter(context, 'Payment Pending');
},
),
_buildStatusTile(
icon: Icons.check_circle_outline,
@ -91,6 +171,9 @@ class ExpenseByStatusWidget extends StatelessWidget {
title: "Pending Approve",
amount: Utils.formatCurrency(data.approvePending.totalAmount),
count: data.approvePending.count.toString(),
onTap: () {
_navigateToExpenseWithFilter(context, 'Approval Pending');
},
),
_buildStatusTile(
icon: Icons.search,
@ -98,6 +181,9 @@ class ExpenseByStatusWidget extends StatelessWidget {
title: "Pending Review",
amount: Utils.formatCurrency(data.reviewPending.totalAmount),
count: data.reviewPending.count.toString(),
onTap: () {
_navigateToExpenseWithFilter(context, 'Review Pending');
},
),
_buildStatusTile(
icon: Icons.insert_drive_file_outlined,
@ -105,27 +191,48 @@ class ExpenseByStatusWidget extends StatelessWidget {
title: "Draft",
amount: Utils.formatCurrency(data.draft.totalAmount),
count: data.draft.count.toString(),
onTap: () {
_navigateToExpenseWithFilter(context, 'Draft');
},
),
const SizedBox(height: 16),
Divider(color: Colors.grey.shade300),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
// Total row tap navigation (no filter)
InkWell(
onTap: _navigateToExpenseWithoutFilter,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.bodyMedium("Project Spendings:", fontWeight: 600),
MyText.bodySmall("(All Processed Payments)",
color: Colors.grey.shade600),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium("Project Spendings:",
fontWeight: 600),
MyText.bodySmall("(All Processed Payments)",
color: Colors.grey.shade600),
],
),
Row(
children: [
MyText.titleLarge(
Utils.formatCurrency(data.totalAmount),
color: Colors.blue,
fontWeight: 700,
),
const SizedBox(width: 6),
const Icon(Icons.chevron_right,
color: Colors.blue, size: 22),
],
)
],
),
MyText.titleLarge(
"${Utils.formatCurrency(data.totalAmount)} >",
color: Colors.blue,
fontWeight: 700,
)
],
),
),
],
),

View File

@ -28,12 +28,17 @@ class _ExpenseMainScreenState extends State<ExpenseMainScreen>
final projectController = Get.find<ProjectController>();
final permissionController = Get.find<PermissionController>();
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
// Delay fetch until after UI & controller are ready
WidgetsBinding.instance.addPostFrameCallback((_) {
final expenseController = Get.find<ExpenseController>();
expenseController.fetchExpenses();
}
});
}
@override
void dispose() {