feat: Enhance monthly expense dashboard with expense type filtering functionality
This commit is contained in:
parent
3c95583a23
commit
ebc8996f71
@ -6,6 +6,7 @@ import 'package:marco/model/dashboard/project_progress_model.dart';
|
|||||||
import 'package:marco/model/dashboard/pending_expenses_model.dart';
|
import 'package:marco/model/dashboard/pending_expenses_model.dart';
|
||||||
import 'package:marco/model/dashboard/expense_type_report_model.dart';
|
import 'package:marco/model/dashboard/expense_type_report_model.dart';
|
||||||
import 'package:marco/model/dashboard/monthly_expence_model.dart';
|
import 'package:marco/model/dashboard/monthly_expence_model.dart';
|
||||||
|
import 'package:marco/model/expense/expense_type_model.dart';
|
||||||
|
|
||||||
class DashboardController extends GetxController {
|
class DashboardController extends GetxController {
|
||||||
// =========================
|
// =========================
|
||||||
@ -78,6 +79,21 @@ class DashboardController extends GetxController {
|
|||||||
MonthlyExpenseDuration.twelveMonths.obs;
|
MonthlyExpenseDuration.twelveMonths.obs;
|
||||||
|
|
||||||
final RxInt selectedMonthsCount = 12.obs;
|
final RxInt selectedMonthsCount = 12.obs;
|
||||||
|
final RxList<ExpenseTypeModel> expenseTypes = <ExpenseTypeModel>[].obs;
|
||||||
|
final Rx<ExpenseTypeModel?> selectedExpenseType = Rx<ExpenseTypeModel?>(null);
|
||||||
|
|
||||||
|
void updateSelectedExpenseType(ExpenseTypeModel? type) {
|
||||||
|
selectedExpenseType.value = type;
|
||||||
|
|
||||||
|
// Debug print to verify
|
||||||
|
print('Selected: ${type?.name ?? "All Types"}');
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
fetchMonthlyExpenses();
|
||||||
|
} else {
|
||||||
|
fetchMonthlyExpenses(categoryId: type.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -188,6 +204,7 @@ class DashboardController extends GetxController {
|
|||||||
endDate: expenseReportEndDate.value,
|
endDate: expenseReportEndDate.value,
|
||||||
),
|
),
|
||||||
fetchMonthlyExpenses(),
|
fetchMonthlyExpenses(),
|
||||||
|
fetchMasterData()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +226,7 @@ class DashboardController extends GetxController {
|
|||||||
selectedMonthsCount.value = 12;
|
selectedMonthsCount.value = 12;
|
||||||
break;
|
break;
|
||||||
case MonthlyExpenseDuration.all:
|
case MonthlyExpenseDuration.all:
|
||||||
selectedMonthsCount.value = 0;
|
selectedMonthsCount.value = 0; // 0 = All months in your API
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +234,18 @@ class DashboardController extends GetxController {
|
|||||||
fetchMonthlyExpenses();
|
fetchMonthlyExpenses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> fetchMasterData() async {
|
||||||
|
try {
|
||||||
|
final expenseTypesData = await ApiService.getMasterExpenseTypes();
|
||||||
|
if (expenseTypesData is List) {
|
||||||
|
expenseTypes.value =
|
||||||
|
expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logSafe('Error fetching master data', level: LogLevel.error, error: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchMonthlyExpenses({String? categoryId}) async {
|
Future<void> fetchMonthlyExpenses({String? categoryId}) async {
|
||||||
try {
|
try {
|
||||||
isMonthlyExpenseLoading.value = true;
|
isMonthlyExpenseLoading.value = true;
|
||||||
|
|||||||
@ -121,6 +121,7 @@ class MonthlyExpenseDashboardChart extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_ChartHeader(
|
_ChartHeader(
|
||||||
|
controller: _controller, // pass controller explicitly
|
||||||
selectedDuration: selectedDuration,
|
selectedDuration: selectedDuration,
|
||||||
onDurationChanged: _controller.updateMonthlyExpenseDuration,
|
onDurationChanged: _controller.updateMonthlyExpenseDuration,
|
||||||
totalExpense: totalExpense,
|
totalExpense: totalExpense,
|
||||||
@ -172,11 +173,13 @@ class MonthlyExpenseDashboardChart extends StatelessWidget {
|
|||||||
class _ChartHeader extends StatelessWidget {
|
class _ChartHeader extends StatelessWidget {
|
||||||
const _ChartHeader({
|
const _ChartHeader({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
required this.controller, // added
|
||||||
required this.selectedDuration,
|
required this.selectedDuration,
|
||||||
required this.onDurationChanged,
|
required this.onDurationChanged,
|
||||||
required this.totalExpense,
|
required this.totalExpense,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final DashboardController controller; // added
|
||||||
final MonthlyExpenseDuration selectedDuration;
|
final MonthlyExpenseDuration selectedDuration;
|
||||||
final ValueChanged<MonthlyExpenseDuration> onDurationChanged;
|
final ValueChanged<MonthlyExpenseDuration> onDurationChanged;
|
||||||
final double totalExpense;
|
final double totalExpense;
|
||||||
@ -189,6 +192,83 @@ class _ChartHeader extends StatelessWidget {
|
|||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
_buildSubtitle(),
|
_buildSubtitle(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// ==========================
|
||||||
|
// Row with popup menu on the right
|
||||||
|
// ==========================
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Obx(() {
|
||||||
|
final selectedType = controller.selectedExpenseType.value;
|
||||||
|
|
||||||
|
return PopupMenuButton<String>(
|
||||||
|
tooltip: 'Filter by Expense Type',
|
||||||
|
onSelected: (String value) {
|
||||||
|
if (value == 'all') {
|
||||||
|
controller.updateSelectedExpenseType(null);
|
||||||
|
} else {
|
||||||
|
final type = controller.expenseTypes
|
||||||
|
.firstWhere((t) => t.id == value);
|
||||||
|
controller.updateSelectedExpenseType(type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) {
|
||||||
|
final types = controller.expenseTypes;
|
||||||
|
return [
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'all',
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text('All Types'),
|
||||||
|
if (selectedType == null)
|
||||||
|
const Icon(Icons.check,
|
||||||
|
size: 16, color: Colors.blueAccent),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...types.map((type) => PopupMenuItem<String>(
|
||||||
|
value: type.id,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(type.name),
|
||||||
|
if (selectedType?.id == type.id)
|
||||||
|
const Icon(Icons.check,
|
||||||
|
size: 16, color: Colors.blueAccent),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
selectedType?.name ?? 'All Types',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
const Icon(Icons.arrow_drop_down, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildDurationSelector(),
|
_buildDurationSelector(),
|
||||||
],
|
],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user