marco.pms.mobileapp/lib/helpers/widgets/dashbaord/expense_by_status_widget.dart
Vaibhav Surve 5c53a3f4be Refactor project structure and rename from 'marco' to 'on field work'
- Updated import paths across multiple files to reflect the new package name.
- Changed application name and identifiers in CMakeLists.txt, Runner.rc, and other configuration files.
- Modified web index.html and manifest.json to update the app title and name.
- Adjusted macOS and Windows project settings to align with the new application name.
- Ensured consistency in naming across all relevant files and directories.
2025-11-22 14:20:37 +05:30

243 lines
8.3 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:on_field_work/controller/dashboard/dashboard_controller.dart';
import 'package:on_field_work/helpers/widgets/my_text.dart';
import 'package:on_field_work/helpers/utils/utils.dart';
import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart';
import 'package:on_field_work/controller/expense/expense_screen_controller.dart';
import 'package:on_field_work/view/expense/expense_screen.dart';
import 'package:collection/collection.dart';
class ExpenseByStatusWidget extends StatelessWidget {
final DashboardController controller;
const ExpenseByStatusWidget({super.key, required this.controller});
Widget _buildStatusTile({
required IconData icon,
required Color color,
required String title,
required String amount,
required String count,
required VoidCallback onTap,
}) {
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),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(title, fontWeight: 600),
const SizedBox(height: 2),
MyText.bodySmall(amount,
color: Colors.indigo, fontWeight: 600),
],
),
),
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(() {
final data = controller.pendingExpensesData.value;
if (controller.isPendingExpensesLoading.value) {
return SkeletonLoaders.expenseByStatusSkeletonLoader();
}
if (data == null) {
return Center(
child: MyText.bodyMedium("No expense status data available"),
);
}
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 6,
spreadRadius: 1,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium("Expense - By Status", fontWeight: 700),
const SizedBox(height: 16),
// ✅ Status tiles
_buildStatusTile(
icon: Icons.currency_rupee,
color: Colors.indigo,
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,
color: Colors.orange,
title: "Pending Approve",
amount: Utils.formatCurrency(data.approvePending.totalAmount),
count: data.approvePending.count.toString(),
onTap: () {
_navigateToExpenseWithFilter(context, 'Approval Pending');
},
),
_buildStatusTile(
icon: Icons.search,
color: Colors.grey.shade700,
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,
color: Colors.cyan,
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),
// ✅ 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: [
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: 600,
),
const SizedBox(width: 6),
const Icon(Icons.chevron_right,
color: Colors.blue, size: 22),
],
)
],
),
),
),
],
),
);
});
}
}