Compare commits
1 Commits
main
...
Vaibhav_Is
Author | SHA1 | Date | |
---|---|---|---|
fa3109e606 |
@ -5,6 +5,9 @@ class ExpenseDetailModel {
|
||||
final PaymentMode paymentMode;
|
||||
final Person paidBy;
|
||||
final Person createdBy;
|
||||
final Person? reviewedBy;
|
||||
final Person? approvedBy;
|
||||
final Person? processedBy;
|
||||
final String transactionDate;
|
||||
final String createdAt;
|
||||
final String supplerName;
|
||||
@ -16,9 +19,11 @@ class ExpenseDetailModel {
|
||||
final String description;
|
||||
final String location;
|
||||
final List<ExpenseDocument> documents;
|
||||
final List<ExpenseLog> expenseLogs;
|
||||
final String? gstNumber;
|
||||
final int noOfPersons;
|
||||
final bool isActive;
|
||||
final dynamic expensesReimburse; // can be replaced with model later
|
||||
|
||||
ExpenseDetailModel({
|
||||
required this.id,
|
||||
@ -27,6 +32,9 @@ class ExpenseDetailModel {
|
||||
required this.paymentMode,
|
||||
required this.paidBy,
|
||||
required this.createdBy,
|
||||
this.reviewedBy,
|
||||
this.approvedBy,
|
||||
this.processedBy,
|
||||
required this.transactionDate,
|
||||
required this.createdAt,
|
||||
required this.supplerName,
|
||||
@ -38,37 +46,70 @@ class ExpenseDetailModel {
|
||||
required this.description,
|
||||
required this.location,
|
||||
required this.documents,
|
||||
required this.expenseLogs,
|
||||
this.gstNumber,
|
||||
required this.noOfPersons,
|
||||
required this.isActive,
|
||||
this.expensesReimburse,
|
||||
});
|
||||
|
||||
factory ExpenseDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
return ExpenseDetailModel(
|
||||
id: json['id'] ?? '',
|
||||
project: json['project'] != null ? Project.fromJson(json['project']) : Project.empty(),
|
||||
expensesType: json['expensesType'] != null ? ExpensesType.fromJson(json['expensesType']) : ExpensesType.empty(),
|
||||
paymentMode: json['paymentMode'] != null ? PaymentMode.fromJson(json['paymentMode']) : PaymentMode.empty(),
|
||||
paidBy: json['paidBy'] != null ? Person.fromJson(json['paidBy']) : Person.empty(),
|
||||
createdBy: json['createdBy'] != null ? Person.fromJson(json['createdBy']) : Person.empty(),
|
||||
project: json['project'] != null
|
||||
? Project.fromJson(json['project'])
|
||||
: Project.empty(),
|
||||
expensesType: json['expensesType'] != null
|
||||
? ExpensesType.fromJson(json['expensesType'])
|
||||
: ExpensesType.empty(),
|
||||
paymentMode: json['paymentMode'] != null
|
||||
? PaymentMode.fromJson(json['paymentMode'])
|
||||
: PaymentMode.empty(),
|
||||
paidBy: json['paidBy'] != null
|
||||
? Person.fromJson(json['paidBy'])
|
||||
: Person.empty(),
|
||||
createdBy: json['createdBy'] != null
|
||||
? Person.fromJson(json['createdBy'])
|
||||
: Person.empty(),
|
||||
reviewedBy:
|
||||
json['reviewedBy'] != null ? Person.fromJson(json['reviewedBy']) : null,
|
||||
approvedBy:
|
||||
json['approvedBy'] != null ? Person.fromJson(json['approvedBy']) : null,
|
||||
processedBy: json['processedBy'] != null
|
||||
? Person.fromJson(json['processedBy'])
|
||||
: null,
|
||||
transactionDate: json['transactionDate'] ?? '',
|
||||
createdAt: json['createdAt'] ?? '',
|
||||
supplerName: json['supplerName'] ?? '',
|
||||
amount: (json['amount'] as num?)?.toDouble() ?? 0.0,
|
||||
status: json['status'] != null ? ExpenseStatus.fromJson(json['status']) : ExpenseStatus.empty(),
|
||||
nextStatus: (json['nextStatus'] as List?)?.map((e) => ExpenseStatus.fromJson(e)).toList() ?? [],
|
||||
status: json['status'] != null
|
||||
? ExpenseStatus.fromJson(json['status'])
|
||||
: ExpenseStatus.empty(),
|
||||
nextStatus: (json['nextStatus'] as List?)
|
||||
?.map((e) => ExpenseStatus.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
preApproved: json['preApproved'] ?? false,
|
||||
transactionId: json['transactionId'] ?? '',
|
||||
description: json['description'] ?? '',
|
||||
location: json['location'] ?? '',
|
||||
documents: (json['documents'] as List?)?.map((e) => ExpenseDocument.fromJson(e)).toList() ?? [],
|
||||
documents: (json['documents'] as List?)
|
||||
?.map((e) => ExpenseDocument.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
expenseLogs: (json['expenseLogs'] as List?)
|
||||
?.map((e) => ExpenseLog.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
gstNumber: json['gstNumber']?.toString(),
|
||||
noOfPersons: json['noOfPersons'] ?? 0,
|
||||
isActive: json['isActive'] ?? true,
|
||||
expensesReimburse: json['expensesReimburse'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Project ----------------
|
||||
class Project {
|
||||
final String id;
|
||||
final String name;
|
||||
@ -115,6 +156,7 @@ class Project {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- ExpensesType ----------------
|
||||
class ExpensesType {
|
||||
final String id;
|
||||
final String name;
|
||||
@ -145,6 +187,7 @@ class ExpensesType {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- PaymentMode ----------------
|
||||
class PaymentMode {
|
||||
final String id;
|
||||
final String name;
|
||||
@ -171,6 +214,7 @@ class PaymentMode {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- Person ----------------
|
||||
class Person {
|
||||
final String id;
|
||||
final String firstName;
|
||||
@ -209,12 +253,13 @@ class Person {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- ExpenseStatus ----------------
|
||||
class ExpenseStatus {
|
||||
final String id;
|
||||
final String name;
|
||||
final String displayName;
|
||||
final String description;
|
||||
final String? permissionIds;
|
||||
final String? permissionIds; // API sends list, but can stringify
|
||||
final String color;
|
||||
final bool isSystem;
|
||||
|
||||
@ -251,6 +296,7 @@ class ExpenseStatus {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- ExpenseDocument ----------------
|
||||
class ExpenseDocument {
|
||||
final String documentId;
|
||||
final String fileName;
|
||||
@ -276,3 +322,32 @@ class ExpenseDocument {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- ExpenseLog ----------------
|
||||
class ExpenseLog {
|
||||
final String id;
|
||||
final Person updatedBy;
|
||||
final String action;
|
||||
final String updateAt;
|
||||
final String comment;
|
||||
|
||||
ExpenseLog({
|
||||
required this.id,
|
||||
required this.updatedBy,
|
||||
required this.action,
|
||||
required this.updateAt,
|
||||
required this.comment,
|
||||
});
|
||||
|
||||
factory ExpenseLog.fromJson(Map<String, dynamic> json) {
|
||||
return ExpenseLog(
|
||||
id: json['id'] ?? '',
|
||||
updatedBy: json['updatedBy'] != null
|
||||
? Person.fromJson(json['updatedBy'])
|
||||
: Person.empty(),
|
||||
action: json['action'] ?? '',
|
||||
updateAt: json['updateAt'] ?? '',
|
||||
comment: json['comment'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||
import 'package:marco/helpers/widgets/my_text.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/model/employee_info.dart';
|
||||
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
class ExpenseDetailScreen extends StatefulWidget {
|
||||
final String expenseId;
|
||||
const ExpenseDetailScreen({super.key, required this.expenseId});
|
||||
@ -121,11 +121,14 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen> {
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_InvoiceDocuments(documents: expense.documents),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
|
||||
_InvoiceTotals(
|
||||
expense: expense,
|
||||
formattedAmount: formattedAmount,
|
||||
statusColor: statusColor,
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
InvoiceLogs(logs: expense.expenseLogs),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -629,6 +632,99 @@ class _InvoiceDocuments extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class InvoiceLogs extends StatelessWidget {
|
||||
final List<ExpenseLog> logs;
|
||||
const InvoiceLogs({required this.logs});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (logs.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: MyText.bodyMedium('No Activity Logs', color: Colors.grey),
|
||||
);
|
||||
}
|
||||
|
||||
final displayedLogs = logs.reversed.toList();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodySmall("Activity Logs:", fontWeight: 600),
|
||||
const SizedBox(height: 16),
|
||||
ListView.builder(
|
||||
itemCount: displayedLogs.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, index) {
|
||||
final log = displayedLogs[index];
|
||||
final formattedDate = DateTimeUtils.convertUtcToLocal(
|
||||
log.updateAt,
|
||||
format: 'dd MMM yyyy hh:mm a',
|
||||
);
|
||||
|
||||
return TimelineTile(
|
||||
alignment: TimelineAlign.start,
|
||||
isFirst: index == 0,
|
||||
isLast: index == displayedLogs.length - 1,
|
||||
indicatorStyle: IndicatorStyle(
|
||||
width: 16,
|
||||
height: 16,
|
||||
indicator: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
beforeLineStyle: LineStyle(color: Colors.grey.shade300, thickness: 2),
|
||||
endChild: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyMedium(
|
||||
"${log.updatedBy.firstName} ${log.updatedBy.lastName}",
|
||||
fontWeight: 600,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
MyText.bodyMedium(
|
||||
log.comment.isNotEmpty ? log.comment : log.action,
|
||||
fontWeight: 500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.access_time, size: 14, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
MyText.bodySmall(formattedDate, color: Colors.grey[700]),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: MyText.bodySmall(
|
||||
log.action,
|
||||
color: Colors.blue.shade700,
|
||||
fontWeight: 600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExpensePermissionHelper {
|
||||
static bool canEditExpense(
|
||||
EmployeeInfo? employee, ExpenseDetailModel expense) {
|
||||
|
@ -79,6 +79,7 @@ dependencies:
|
||||
quill_delta: ^3.0.0-nullsafety.2
|
||||
connectivity_plus: ^6.1.4
|
||||
geocoding: ^4.0.0
|
||||
timeline_tile: ^2.0.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
Loading…
x
Reference in New Issue
Block a user