feat(expense): implement update expense status functionality and UI integration

This commit is contained in:
Vaibhav Surve 2025-07-21 18:35:27 +05:30
parent debc12bc1b
commit e0ed35a671
4 changed files with 141 additions and 6 deletions

View File

@ -39,4 +39,34 @@ class ExpenseController extends GetxController {
isLoading.value = false;
}
}
/// Update expense status and refresh the list
Future<bool> updateExpenseStatus(String expenseId, String statusId) async {
isLoading.value = true;
errorMessage.value = '';
try {
logSafe("Updating status for expense: $expenseId -> $statusId");
final success = await ApiService.updateExpenseStatusApi(
expenseId: expenseId,
statusId: statusId,
);
if (success) {
logSafe("Expense status updated successfully.");
await fetchExpenses();
return true;
} else {
errorMessage.value = "Failed to update expense status.";
return false;
}
} catch (e, stack) {
errorMessage.value = 'An unexpected error occurred.';
logSafe("Exception in updateExpenseStatus: $e", level: LogLevel.error);
logSafe("StackTrace: $stack", level: LogLevel.debug);
return false;
} finally {
isLoading.value = false;
}
}
}

View File

@ -59,5 +59,5 @@ class ApiEndpoints {
static const String getMasterPaymentModes = "/master/payment-modes";
static const String getMasterExpenseStatus = "/master/expenses-status";
static const String getMasterExpenseTypes = "/master/expenses-types";
static const String updateExpenseStatus = "/expense/action";
}

View File

@ -241,6 +241,51 @@ class ApiService {
// === Expense APIs === //
/// Update Expense Status API
static Future<bool> updateExpenseStatusApi({
required String expenseId,
required String statusId,
}) async {
final payload = {
"expenseId": expenseId,
"statusId": statusId,
};
const endpoint = ApiEndpoints.updateExpenseStatus;
logSafe("Updating expense status with payload: $payload");
try {
final response =
await _postRequest(endpoint, payload, customTimeout: extendedTimeout);
if (response == null) {
logSafe("Update expense status failed: null response",
level: LogLevel.error);
return false;
}
logSafe("Update expense status response status: ${response.statusCode}");
logSafe("Update expense status response body: ${response.body}");
final json = jsonDecode(response.body);
if (json['success'] == true) {
logSafe("Expense status updated successfully: ${json['data']}");
return true;
} else {
logSafe(
"Failed to update expense status: ${json['message'] ?? 'Unknown error'}",
level: LogLevel.warning,
);
}
} catch (e, stack) {
logSafe("Exception during updateExpenseStatus API: $e",
level: LogLevel.error);
logSafe("StackTrace: $stack", level: LogLevel.debug);
}
return false;
}
static Future<List<dynamic>?> getExpenseListApi() async {
const endpoint = ApiEndpoints.getExpenseList;

View File

@ -1,10 +1,11 @@
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/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/model/expense/expense_list_model.dart';
import 'package:marco/helpers/utils/date_time_utils.dart'; // Import DateTimeUtils
import 'package:marco/helpers/utils/date_time_utils.dart';
class ExpenseDetailScreen extends StatelessWidget {
const ExpenseDetailScreen({super.key});
@ -31,6 +32,9 @@ class ExpenseDetailScreen extends StatelessWidget {
final ExpenseModel expense = Get.arguments['expense'] as ExpenseModel;
final statusColor = getStatusColor(expense.status.name);
final projectController = Get.find<ProjectController>();
final expenseController = Get.find<ExpenseController>();
print(
"Next Status List: ${expense.nextStatus.map((e) => e.toJson()).toList()}");
return Scaffold(
backgroundColor: const Color(0xFFF7F7F7),
@ -110,6 +114,64 @@ class ExpenseDetailScreen extends StatelessWidget {
],
),
),
bottomNavigationBar: expense.nextStatus.isNotEmpty
? SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: expense.nextStatus.map((next) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size(100, 40),
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 12),
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
onPressed: () async {
final success =
await expenseController.updateExpenseStatus(
expense.id,
next.id,
);
if (success) {
Get.snackbar(
'Success',
'Expense moved to ${next.name}',
backgroundColor: Colors.green.withOpacity(0.8),
colorText: Colors.white,
);
Get.back(result: true);
} else {
Get.snackbar(
'Error',
'Failed to update status.',
backgroundColor: Colors.red.withOpacity(0.8),
colorText: Colors.white,
);
}
},
child: Text(
next.name,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
);
}).toList(),
),
),
)
: null,
);
}
}
@ -228,8 +290,7 @@ class _ExpenseDetailsList extends StatelessWidget {
_DetailRow(title: "Payment Mode", value: expense.paymentMode.name),
_DetailRow(
title: "Paid By",
value:
'${expense.paidBy.firstName} ${expense.paidBy.lastName}'),
value: '${expense.paidBy.firstName} ${expense.paidBy.lastName}'),
_DetailRow(
title: "Created By",
value:
@ -243,8 +304,7 @@ class _ExpenseDetailsList extends StatelessWidget {
title: "Next Status",
value: expense.nextStatus.map((e) => e.name).join(", ")),
_DetailRow(
title: "Pre-Approved",
value: expense.preApproved ? "Yes" : "No"),
title: "Pre-Approved", value: expense.preApproved ? "Yes" : "No"),
],
),
);