marco.pms.mobileapp/lib/view/expense/expense_detail_screen.dart

353 lines
11 KiB
Dart

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';
class ExpenseDetailScreen extends StatelessWidget {
const ExpenseDetailScreen({super.key});
static Color getStatusColor(String? status) {
switch (status) {
case 'Requested':
return Colors.blue;
case 'Review':
return Colors.orange;
case 'Approved':
return Colors.green;
case 'Paid':
return Colors.purple;
case 'Closed':
return Colors.grey;
default:
return Colors.black;
}
}
@override
Widget build(BuildContext context) {
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),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(72),
child: AppBar(
backgroundColor: Colors.white,
elevation: 1,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: MySpacing.xy(16, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new,
color: Colors.black, size: 20),
onPressed: () => Get.back(),
),
MySpacing.width(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
MyText.titleLarge(
'Expense Details',
fontWeight: 700,
color: Colors.black,
),
MySpacing.height(2),
Obx(() {
final projectName =
projectController.selectedProject?.name ??
'Select Project';
return InkWell(
onTap: () => Get.toNamed('/project-selector'),
child: Row(
children: [
const Icon(Icons.work_outline,
size: 14, color: Colors.grey),
MySpacing.width(4),
Expanded(
child: MyText.bodySmall(
projectName,
fontWeight: 600,
overflow: TextOverflow.ellipsis,
color: Colors.grey[700],
),
),
],
),
);
}),
],
),
),
],
),
),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ExpenseHeader(
title: expense.expensesType.name,
amount: '${expense.amount.toStringAsFixed(2)}',
status: expense.status.name,
statusColor: statusColor,
),
const SizedBox(height: 16),
_ExpenseDetailsList(expense: expense),
],
),
),
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,
);
}
}
class _ExpenseHeader extends StatelessWidget {
final String title;
final String amount;
final String status;
final Color statusColor;
const _ExpenseHeader({
required this.title,
required this.amount,
required this.status,
required this.statusColor,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 6),
Text(
amount,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w700,
color: Colors.black,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.flag, size: 16, color: statusColor),
const SizedBox(width: 6),
Text(
status,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
}
class _ExpenseDetailsList extends StatelessWidget {
final ExpenseModel expense;
const _ExpenseDetailsList({required this.expense});
@override
Widget build(BuildContext context) {
final transactionDate = DateTimeUtils.convertUtcToLocal(
expense.transactionDate.toString(),
format: 'dd-MM-yyyy hh:mm a',
);
final createdAt = DateTimeUtils.convertUtcToLocal(
expense.createdAt.toString(),
format: 'dd-MM-yyyy hh:mm a',
);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_DetailRow(title: "Project", value: expense.project.name),
_DetailRow(title: "Expense Type", value: expense.expensesType.name),
_DetailRow(title: "Payment Mode", value: expense.paymentMode.name),
_DetailRow(
title: "Paid By",
value: '${expense.paidBy.firstName} ${expense.paidBy.lastName}'),
_DetailRow(
title: "Created By",
value:
'${expense.createdBy.firstName} ${expense.createdBy.lastName}'),
_DetailRow(title: "Transaction Date", value: transactionDate),
_DetailRow(title: "Created At", value: createdAt),
_DetailRow(title: "Supplier Name", value: expense.supplerName),
_DetailRow(title: "Amount", value: '${expense.amount}'),
_DetailRow(title: "Status", value: expense.status.name),
_DetailRow(
title: "Next Status",
value: expense.nextStatus.map((e) => e.name).join(", ")),
_DetailRow(
title: "Pre-Approved", value: expense.preApproved ? "Yes" : "No"),
],
),
);
}
}
class _DetailRow extends StatelessWidget {
final String title;
final String value;
const _DetailRow({required this.title, required this.value});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
title,
style: const TextStyle(
fontSize: 13,
color: Colors.grey,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
flex: 5,
child: Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
}