optimized code
This commit is contained in:
parent
7ce0a8555a
commit
fbfc54159c
@ -4,6 +4,9 @@ import 'package:on_field_work/helpers/services/app_logger.dart';
|
||||
import 'package:on_field_work/model/expense/expense_detail_model.dart';
|
||||
import 'package:on_field_work/model/employees/employee_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:on_field_work/model/employees/employee_info.dart';
|
||||
import 'package:on_field_work/helpers/services/storage/local_storage.dart';
|
||||
|
||||
|
||||
class ExpenseDetailController extends GetxController {
|
||||
final Rx<ExpenseDetailModel?> expense = Rx<ExpenseDetailModel?>(null);
|
||||
@ -16,6 +19,22 @@ class ExpenseDetailController extends GetxController {
|
||||
bool _isInitialized = false;
|
||||
final employeeSearchController = TextEditingController();
|
||||
final isSearchingEmployees = false.obs;
|
||||
|
||||
// NEW: Holds the logged-in user info for permission checks
|
||||
EmployeeInfo? employeeInfo;
|
||||
final RxBool canSubmit = false.obs;
|
||||
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_loadEmployeeInfo(); // Load employee info on init
|
||||
}
|
||||
|
||||
void _loadEmployeeInfo() async {
|
||||
final info = await LocalStorage.getEmployeeInfo();
|
||||
employeeInfo = info;
|
||||
}
|
||||
|
||||
/// Call this once from the screen (NOT inside build) to initialize
|
||||
void init(String expenseId) {
|
||||
@ -31,6 +50,36 @@ class ExpenseDetailController extends GetxController {
|
||||
]);
|
||||
}
|
||||
|
||||
/// NEW: Logic to check if the current user can submit the expense
|
||||
void checkPermissionToSubmit() {
|
||||
final expenseData = expense.value;
|
||||
if (employeeInfo == null || expenseData == null) {
|
||||
canSubmit.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Status ID for 'Submit' (Hardcoded ID from the original screen logic)
|
||||
const allowedNextStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
|
||||
final isCreatedByCurrentUser = employeeInfo?.id == expenseData.createdBy.id;
|
||||
final nextStatusIds = expenseData.nextStatus.map((e) => e.id).toList();
|
||||
final hasRequiredNextStatus = nextStatusIds.contains(allowedNextStatusId);
|
||||
|
||||
final result = isCreatedByCurrentUser && hasRequiredNextStatus;
|
||||
|
||||
logSafe(
|
||||
'🐛 Checking submit permission:\n'
|
||||
'🐛 - Logged-in employee ID: ${employeeInfo?.id}\n'
|
||||
'🐛 - Expense created by ID: ${expenseData.createdBy.id}\n'
|
||||
'🐛 - Next Status IDs: $nextStatusIds\n'
|
||||
'🐛 - Has Required Next Status ID ($allowedNextStatusId): $hasRequiredNextStatus\n'
|
||||
'🐛 - Final Permission Result: $result',
|
||||
level: LogLevel.debug,
|
||||
);
|
||||
|
||||
canSubmit.value = result;
|
||||
}
|
||||
|
||||
/// Generic method to handle API calls with loading and error states
|
||||
Future<T?> _apiCallWrapper<T>(
|
||||
Future<T?> Function() apiCall, String operationName) async {
|
||||
@ -63,6 +112,8 @@ class ExpenseDetailController extends GetxController {
|
||||
try {
|
||||
expense.value = ExpenseDetailModel.fromJson(result);
|
||||
logSafe("Expense details loaded successfully: ${expense.value?.id}");
|
||||
// Call permission check after data is loaded
|
||||
checkPermissionToSubmit();
|
||||
} catch (e) {
|
||||
errorMessage.value = 'Failed to parse expense details: $e';
|
||||
logSafe("Parse error in fetchExpenseDetails: $e",
|
||||
@ -75,8 +126,6 @@ class ExpenseDetailController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// This method seems like a utility and might be better placed in a helper or utility class
|
||||
// if it's used across multiple controllers. Keeping it here for now as per original code.
|
||||
List<String> parsePermissionIds(dynamic permissionData) {
|
||||
if (permissionData == null) return [];
|
||||
if (permissionData is List) {
|
||||
@ -131,8 +180,6 @@ class ExpenseDetailController extends GetxController {
|
||||
allEmployees.clear();
|
||||
logSafe("No employees found.", level: LogLevel.warning);
|
||||
}
|
||||
// `update()` is typically not needed for RxList directly unless you have specific GetBuilder/Obx usage that requires it
|
||||
// If you are using Obx widgets, `allEmployees.assignAll` will automatically trigger a rebuild.
|
||||
}
|
||||
|
||||
/// Update expense with reimbursement info and status
|
||||
@ -191,4 +238,4 @@ class ExpenseDetailController extends GetxController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,14 +11,12 @@ import 'package:on_field_work/model/expense/comment_bottom_sheet.dart';
|
||||
import 'package:on_field_work/model/expense/expense_detail_model.dart';
|
||||
import 'package:on_field_work/model/expense/reimbursement_bottom_sheet.dart';
|
||||
import 'package:on_field_work/controller/expense/add_expense_controller.dart';
|
||||
import 'package:on_field_work/helpers/services/app_logger.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
||||
import 'package:on_field_work/helpers/widgets/expense/expense_detail_helpers.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||
import 'package:on_field_work/helpers/services/storage/local_storage.dart';
|
||||
import 'package:on_field_work/model/employees/employee_info.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||
@ -37,15 +35,14 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen>
|
||||
final projectController = Get.find<ProjectController>();
|
||||
final permissionController = Get.put(PermissionController());
|
||||
|
||||
EmployeeInfo? employeeInfo;
|
||||
final RxBool canSubmit = false.obs;
|
||||
bool _checkedPermission = false;
|
||||
// Removed local employeeInfo, canSubmit, and _checkedPermission
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.put(ExpenseDetailController(), tag: widget.expenseId);
|
||||
controller.init(widget.expenseId);
|
||||
_loadEmployeeInfo();
|
||||
// EmployeeInfo loading and permission checking is now handled inside controller.init()
|
||||
controller.init(widget.expenseId);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -54,271 +51,239 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _loadEmployeeInfo() async {
|
||||
final info = await LocalStorage.getEmployeeInfo();
|
||||
employeeInfo = info;
|
||||
}
|
||||
// Removed _loadEmployeeInfo and _checkPermissionToSubmit
|
||||
|
||||
void _checkPermissionToSubmit(ExpenseDetailModel expense) {
|
||||
const allowedNextStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color appBarColor = contentTheme.primary;
|
||||
|
||||
final isCreatedByCurrentUser = employeeInfo?.id == expense.createdBy.id;
|
||||
final nextStatusIds = expense.nextStatus.map((e) => e.id).toList();
|
||||
final hasRequiredNextStatus = nextStatusIds.contains(allowedNextStatusId);
|
||||
|
||||
final result = isCreatedByCurrentUser && hasRequiredNextStatus;
|
||||
|
||||
logSafe(
|
||||
'🐛 Checking submit permission:\n'
|
||||
'🐛 - Logged-in employee ID: ${employeeInfo?.id}\n'
|
||||
'🐛 - Expense created by ID: ${expense.createdBy.id}\n'
|
||||
'🐛 - Next Status IDs: $nextStatusIds\n'
|
||||
'🐛 - Has Required Next Status ID ($allowedNextStatusId): $hasRequiredNextStatus\n'
|
||||
'🐛 - Final Permission Result: $result',
|
||||
level: LogLevel.debug,
|
||||
);
|
||||
|
||||
canSubmit.value = result;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color appBarColor = contentTheme.primary;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F7F7),
|
||||
appBar: CustomAppBar(
|
||||
title: "Expense Details",
|
||||
backgroundColor: appBarColor,
|
||||
onBackPressed: () => Get.toNamed('/dashboard/expense-main-page'),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
// Gradient behind content
|
||||
Container(
|
||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
appBarColor,
|
||||
appBarColor.withOpacity(0.0),
|
||||
],
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F7F7),
|
||||
appBar: CustomAppBar(
|
||||
title: "Expense Details",
|
||||
backgroundColor: appBarColor,
|
||||
onBackPressed: () => Get.toNamed('/dashboard/expense-main-page'),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
// Gradient behind content
|
||||
Container(
|
||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
appBarColor,
|
||||
appBarColor.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Main content
|
||||
SafeArea(
|
||||
child: Obx(() {
|
||||
if (controller.isLoading.value) return buildLoadingSkeleton();
|
||||
// Main content
|
||||
SafeArea(
|
||||
child: Obx(() {
|
||||
if (controller.isLoading.value) return buildLoadingSkeleton();
|
||||
|
||||
final expense = controller.expense.value;
|
||||
if (controller.errorMessage.isNotEmpty || expense == null) {
|
||||
return Center(child: MyText.bodyMedium("No data to display."));
|
||||
}
|
||||
final expense = controller.expense.value;
|
||||
if (controller.errorMessage.isNotEmpty || expense == null) {
|
||||
return Center(child: MyText.bodyMedium("No data to display."));
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkPermissionToSubmit(expense);
|
||||
});
|
||||
// Permission logic moved to controller (no need for postFrameCallback here)
|
||||
|
||||
final statusColor = getExpenseStatusColor(
|
||||
expense.status.name,
|
||||
colorCode: expense.status.color,
|
||||
);
|
||||
final formattedAmount = formatExpenseAmount(expense.amount);
|
||||
final statusColor = getExpenseStatusColor(
|
||||
expense.status.name,
|
||||
colorCode: expense.status.color,
|
||||
);
|
||||
final formattedAmount = formatExpenseAmount(expense.amount);
|
||||
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await controller.fetchExpenseDetails();
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
12, 12, 12, 30 + MediaQuery.of(context).padding.bottom
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
elevation: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14, horizontal: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header & Status
|
||||
_InvoiceHeader(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await controller.fetchExpenseDetails();
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
12, 12, 12, 30 + MediaQuery.of(context).padding.bottom),
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
elevation: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14, horizontal: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header & Status
|
||||
_InvoiceHeader(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Activity Logs
|
||||
InvoiceLogs(logs: expense.expenseLogs),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
// Activity Logs
|
||||
InvoiceLogs(logs: expense.expenseLogs),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Amount & Summary
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyMedium('Amount', fontWeight: 600),
|
||||
const SizedBox(height: 4),
|
||||
MyText.bodyLarge(
|
||||
formattedAmount,
|
||||
fontWeight: 700,
|
||||
color: statusColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (expense.preApproved)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: MyText.bodySmall(
|
||||
'Pre-Approved',
|
||||
fontWeight: 600,
|
||||
color: Colors.green,
|
||||
),
|
||||
// Amount & Summary
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyMedium('Amount',
|
||||
fontWeight: 600),
|
||||
const SizedBox(height: 4),
|
||||
MyText.bodyLarge(
|
||||
formattedAmount,
|
||||
fontWeight: 700,
|
||||
color: statusColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
const Spacer(),
|
||||
if (expense.preApproved)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: MyText.bodySmall(
|
||||
'Pre-Approved',
|
||||
fontWeight: 600,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Parties
|
||||
_InvoicePartiesTable(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
// Parties
|
||||
_InvoicePartiesTable(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Expense Details
|
||||
_InvoiceDetailsTable(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
// Expense Details
|
||||
_InvoiceDetailsTable(expense: expense),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Documents
|
||||
_InvoiceDocuments(documents: expense.documents),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
// Documents
|
||||
_InvoiceDocuments(documents: expense.documents),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
// Totals
|
||||
_InvoiceTotals(
|
||||
expense: expense,
|
||||
formattedAmount: formattedAmount,
|
||||
statusColor: statusColor,
|
||||
),
|
||||
],
|
||||
// Totals
|
||||
_InvoiceTotals(
|
||||
expense: expense,
|
||||
formattedAmount: formattedAmount,
|
||||
statusColor: statusColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(() {
|
||||
if (controller.isLoading.value) return buildLoadingSkeleton();
|
||||
|
||||
final expense = controller.expense.value;
|
||||
if (controller.errorMessage.isNotEmpty || expense == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (!_checkedPermission) {
|
||||
_checkedPermission = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkPermissionToSubmit(expense);
|
||||
});
|
||||
}
|
||||
|
||||
if (!ExpensePermissionHelper.canEditExpense(employeeInfo, expense)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final editData = {
|
||||
'id': expense.id,
|
||||
'projectName': expense.project.name,
|
||||
'amount': expense.amount,
|
||||
'supplerName': expense.supplerName,
|
||||
'description': expense.description,
|
||||
'transactionId': expense.transactionId,
|
||||
'location': expense.location,
|
||||
'transactionDate': expense.transactionDate,
|
||||
'noOfPersons': expense.noOfPersons,
|
||||
'expensesTypeId': expense.expensesType.id,
|
||||
'paymentModeId': expense.paymentMode.id,
|
||||
'paidById': expense.paidBy.id,
|
||||
'paidByFirstName': expense.paidBy.firstName,
|
||||
'paidByLastName': expense.paidBy.lastName,
|
||||
'attachments': expense.documents
|
||||
.map((doc) => {
|
||||
'url': doc.preSignedUrl,
|
||||
'fileName': doc.fileName,
|
||||
'documentId': doc.documentId,
|
||||
'contentType': doc.contentType,
|
||||
})
|
||||
.toList(),
|
||||
};
|
||||
|
||||
final addCtrl = Get.put(AddExpenseController());
|
||||
await addCtrl.loadMasterData();
|
||||
addCtrl.populateFieldsForEdit(editData);
|
||||
|
||||
await showAddExpenseBottomSheet(isEdit: true);
|
||||
await controller.fetchExpenseDetails();
|
||||
},
|
||||
backgroundColor: contentTheme.primary,
|
||||
icon: const Icon(Icons.edit),
|
||||
label: MyText.bodyMedium("Edit Expense",
|
||||
fontWeight: 600, color: Colors.white),
|
||||
);
|
||||
}),
|
||||
bottomNavigationBar: Obx(() {
|
||||
final expense = controller.expense.value;
|
||||
if (expense == null) return const SizedBox();
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: Color(0x11000000))),
|
||||
);
|
||||
}),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: expense.nextStatus.where((next) {
|
||||
const submitStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(() {
|
||||
final expense = controller.expense.value;
|
||||
if (controller.errorMessage.isNotEmpty || expense == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final rawPermissions = next.permissionIds;
|
||||
final parsedPermissions =
|
||||
controller.parsePermissionIds(rawPermissions);
|
||||
// Removed _checkedPermission and its logic
|
||||
|
||||
final isSubmitStatus = next.id == submitStatusId;
|
||||
final isCreatedByCurrentUser =
|
||||
employeeInfo?.id == expense.createdBy.id;
|
||||
if (!ExpensePermissionHelper.canEditExpense(
|
||||
controller.employeeInfo, // Use controller's employeeInfo
|
||||
expense)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (isSubmitStatus) return isCreatedByCurrentUser;
|
||||
return permissionController.hasAnyPermission(parsedPermissions);
|
||||
}).map((next) {
|
||||
return _statusButton(context, controller, expense, next);
|
||||
}).toList(),
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final editData = {
|
||||
'id': expense.id,
|
||||
'projectName': expense.project.name,
|
||||
'amount': expense.amount,
|
||||
'supplerName': expense.supplerName,
|
||||
'description': expense.description,
|
||||
'transactionId': expense.transactionId,
|
||||
'location': expense.location,
|
||||
'transactionDate': expense.transactionDate,
|
||||
'noOfPersons': expense.noOfPersons,
|
||||
'expensesTypeId': expense.expensesType.id,
|
||||
'paymentModeId': expense.paymentMode.id,
|
||||
'paidById': expense.paidBy.id,
|
||||
'paidByFirstName': expense.paidBy.firstName,
|
||||
'paidByLastName': expense.paidBy.lastName,
|
||||
'attachments': expense.documents
|
||||
.map((doc) => {
|
||||
'url': doc.preSignedUrl,
|
||||
'fileName': doc.fileName,
|
||||
'documentId': doc.documentId,
|
||||
'contentType': doc.contentType,
|
||||
})
|
||||
.toList(),
|
||||
};
|
||||
|
||||
final addCtrl = Get.put(AddExpenseController());
|
||||
await addCtrl.loadMasterData();
|
||||
addCtrl.populateFieldsForEdit(editData);
|
||||
|
||||
await showAddExpenseBottomSheet(isEdit: true);
|
||||
await controller.fetchExpenseDetails();
|
||||
},
|
||||
backgroundColor: contentTheme.primary,
|
||||
icon: const Icon(Icons.edit),
|
||||
label: MyText.bodyMedium("Edit Expense",
|
||||
fontWeight: 600, color: Colors.white),
|
||||
);
|
||||
}),
|
||||
bottomNavigationBar: Obx(() {
|
||||
final expense = controller.expense.value;
|
||||
if (expense == null) return const SizedBox();
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: Color(0x11000000))),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: expense.nextStatus.where((next) {
|
||||
const submitStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
|
||||
final rawPermissions = next.permissionIds;
|
||||
final parsedPermissions =
|
||||
controller.parsePermissionIds(rawPermissions);
|
||||
|
||||
final isSubmitStatus = next.id == submitStatusId;
|
||||
final isCreatedByCurrentUser =
|
||||
controller.employeeInfo?.id == expense.createdBy.id; // Use controller's employeeInfo
|
||||
|
||||
if (isSubmitStatus) return isCreatedByCurrentUser;
|
||||
return permissionController.hasAnyPermission(parsedPermissions);
|
||||
}).map((next) {
|
||||
return _statusButton(context, controller, expense, next);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _statusButton(BuildContext context, ExpenseDetailController controller,
|
||||
ExpenseDetailModel expense, dynamic next) {
|
||||
@ -346,7 +311,8 @@ Widget build(BuildContext context) {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(5))),
|
||||
borderRadius:
|
||||
BorderRadius.vertical(top: Radius.circular(5))),
|
||||
builder: (context) => ReimbursementBottomSheet(
|
||||
expenseId: expense.id,
|
||||
statusId: next.id,
|
||||
@ -819,4 +785,4 @@ class _InvoiceTotals extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user