resoled payment request model crash issue

This commit is contained in:
Vaibhav Surve 2025-11-18 10:57:46 +05:30
parent 618ac6f27a
commit dc4ea7979c
2 changed files with 473 additions and 416 deletions

View File

@ -1,30 +1,32 @@
class PaymentRequestDetail { class PaymentRequestDetail {
bool success; final bool? success;
String message; final String? message;
PaymentRequestData? data; final PaymentRequestData? data;
dynamic errors; final dynamic errors;
int statusCode; final int? statusCode;
DateTime timestamp; final DateTime? timestamp;
PaymentRequestDetail({ PaymentRequestDetail({
required this.success, this.success,
required this.message, this.message,
this.data, this.data,
this.errors, this.errors,
required this.statusCode, this.statusCode,
required this.timestamp, this.timestamp,
}); });
factory PaymentRequestDetail.fromJson(Map<String, dynamic> json) => factory PaymentRequestDetail.fromJson(Map<String, dynamic> json) =>
PaymentRequestDetail( PaymentRequestDetail(
success: json['success'], success: json['success'] as bool?,
message: json['message'], message: json['message'] as String?,
data: json['data'] != null data: json['data'] != null
? PaymentRequestData.fromJson(json['data']) ? PaymentRequestData.fromJson(json['data'])
: null, : null,
errors: json['errors'], errors: json['errors'],
statusCode: json['statusCode'], statusCode: json['statusCode'] as int?,
timestamp: DateTime.parse(json['timestamp']), timestamp: json['timestamp'] != null
? DateTime.tryParse(json['timestamp'])
: null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -33,117 +35,129 @@ class PaymentRequestDetail {
'data': data?.toJson(), 'data': data?.toJson(),
'errors': errors, 'errors': errors,
'statusCode': statusCode, 'statusCode': statusCode,
'timestamp': timestamp.toIso8601String(), 'timestamp': timestamp?.toIso8601String(),
}; };
} }
class PaymentRequestData { class PaymentRequestData {
String id; final String? id;
String title; final String? title;
String description; final String? description;
String paymentRequestUID; final String? paymentRequestUID;
String payee; final String? payee;
Currency currency; final Currency? currency;
double amount; final double? amount;
double? baseAmount; final double? baseAmount;
double? taxAmount; final double? taxAmount;
DateTime dueDate; final DateTime? dueDate;
Project project; final Project? project;
dynamic recurringPayment; final RecurringPayment? recurringPayment;
ExpenseCategory expenseCategory; final ExpenseCategory? expenseCategory;
ExpenseStatus expenseStatus; final ExpenseStatus? expenseStatus;
String? paidTransactionId; final String? paidTransactionId;
DateTime? paidAt; final DateTime? paidAt;
User? paidBy; final User? paidBy;
bool isAdvancePayment; final bool? isAdvancePayment;
DateTime createdAt; final DateTime? createdAt;
User createdBy; final User? createdBy;
DateTime updatedAt; final DateTime? updatedAt;
User? updatedBy; final User? updatedBy;
List<NextStatus> nextStatus; final List<NextStatus>? nextStatus;
List<UpdateLog> updateLogs; final List<UpdateLog>? updateLogs;
List<Attachment> attachments; final List<Attachment>? attachments;
bool isActive; final bool? isActive;
bool isExpenseCreated; final bool? isExpenseCreated;
PaymentRequestData({ PaymentRequestData({
required this.id, this.id,
required this.title, this.title,
required this.description, this.description,
required this.paymentRequestUID, this.paymentRequestUID,
required this.payee, this.payee,
required this.currency, this.currency,
required this.amount, this.amount,
this.baseAmount, this.baseAmount,
this.taxAmount, this.taxAmount,
required this.dueDate, this.dueDate,
required this.project, this.project,
this.recurringPayment, this.recurringPayment,
required this.expenseCategory, this.expenseCategory,
required this.expenseStatus, this.expenseStatus,
this.paidTransactionId, this.paidTransactionId,
this.paidAt, this.paidAt,
this.paidBy, this.paidBy,
required this.isAdvancePayment, this.isAdvancePayment,
required this.createdAt, this.createdAt,
required this.createdBy, this.createdBy,
required this.updatedAt, this.updatedAt,
this.updatedBy, this.updatedBy,
required this.nextStatus, this.nextStatus,
required this.updateLogs, this.updateLogs,
required this.attachments, this.attachments,
required this.isActive, this.isActive,
required this.isExpenseCreated, this.isExpenseCreated,
}); });
factory PaymentRequestData.fromJson(Map<String, dynamic> json) => factory PaymentRequestData.fromJson(Map<String, dynamic> json) =>
PaymentRequestData( PaymentRequestData(
id: json['id'], id: json['id'] as String?,
title: json['title'], title: json['title'] as String?,
description: json['description'], description: json['description'] as String?,
paymentRequestUID: json['paymentRequestUID'], paymentRequestUID: json['paymentRequestUID'] as String?,
payee: json['payee'], payee: json['payee'] as String?,
currency: Currency.fromJson(json['currency']), currency: json['currency'] != null
amount: (json['amount'] as num).toDouble(), ? Currency.fromJson(json['currency'])
baseAmount: json['baseAmount'] != null
? (json['baseAmount'] as num).toDouble()
: null, : null,
taxAmount: json['taxAmount'] != null amount: (json['amount'] as num?)?.toDouble(),
? (json['taxAmount'] as num).toDouble() baseAmount: (json['baseAmount'] as num?)?.toDouble(),
taxAmount: (json['taxAmount'] as num?)?.toDouble(),
dueDate: json['dueDate'] != null
? DateTime.tryParse(json['dueDate'])
: null,
project: json['project'] != null
? Project.fromJson(json['project'])
: null, : null,
dueDate: DateTime.parse(json['dueDate']),
project: Project.fromJson(json['project']),
recurringPayment: json['recurringPayment'] != null recurringPayment: json['recurringPayment'] != null
? RecurringPayment.fromJson(json['recurringPayment']) ? RecurringPayment.fromJson(json['recurringPayment'])
: null, : null,
expenseCategory: ExpenseCategory.fromJson(json['expenseCategory']), expenseCategory: json['expenseCategory'] != null
expenseStatus: ExpenseStatus.fromJson(json['expenseStatus']), ? ExpenseCategory.fromJson(json['expenseCategory'])
paidTransactionId: json['paidTransactionId'], : null,
paidAt: json['paidAt'] != null ? DateTime.parse(json['paidAt']) : null, expenseStatus: json['expenseStatus'] != null
paidBy: json['paidBy'] != null ? User.fromJson(json['paidBy']) : null, ? ExpenseStatus.fromJson(json['expenseStatus'])
isAdvancePayment: json['isAdvancePayment'] ?? false, : null,
createdAt: DateTime.parse(json['createdAt']), paidTransactionId: json['paidTransactionId'] as String?,
createdBy: User.fromJson(json['createdBy']), paidAt: json['paidAt'] != null
updatedAt: DateTime.parse(json['updatedAt']), ? DateTime.tryParse(json['paidAt'])
: null,
paidBy:
json['paidBy'] != null ? User.fromJson(json['paidBy']) : null,
isAdvancePayment: json['isAdvancePayment'] as bool?,
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'])
: null,
createdBy: json['createdBy'] != null
? User.fromJson(json['createdBy'])
: null,
updatedAt: json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'])
: null,
updatedBy: updatedBy:
json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null, json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null,
nextStatus: (json['nextStatus'] != null nextStatus: (json['nextStatus'] as List?)
? (json['nextStatus'] as List<dynamic>) ?.map((e) => NextStatus.fromJson(e))
.map((e) => NextStatus.fromJson(e)) .toList() ??
.toList() [],
: <NextStatus>[]), updateLogs: (json['updateLogs'] as List?)
updateLogs: (json['updateLogs'] != null ?.map((e) => UpdateLog.fromJson(e))
? (json['updateLogs'] as List<dynamic>) .toList() ??
.map((e) => UpdateLog.fromJson(e)) [],
.toList() attachments: (json['attachments'] as List?)
: <UpdateLog>[]), ?.map((e) => Attachment.fromJson(e))
attachments: (json['attachments'] != null .toList() ??
? (json['attachments'] as List<dynamic>) [],
.map((e) => Attachment.fromJson(e)) isActive: json['isActive'] as bool?,
.toList() isExpenseCreated: json['isExpenseCreated'] as bool?,
: <Attachment>[]),
isActive: json['isActive'] ?? true,
isExpenseCreated: json['isExpenseCreated'] ?? false,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -152,50 +166,50 @@ class PaymentRequestData {
'description': description, 'description': description,
'paymentRequestUID': paymentRequestUID, 'paymentRequestUID': paymentRequestUID,
'payee': payee, 'payee': payee,
'currency': currency.toJson(), 'currency': currency?.toJson(),
'amount': amount, 'amount': amount,
'baseAmount': baseAmount, 'baseAmount': baseAmount,
'taxAmount': taxAmount, 'taxAmount': taxAmount,
'dueDate': dueDate.toIso8601String(), 'dueDate': dueDate?.toIso8601String(),
'project': project.toJson(), 'project': project?.toJson(),
'recurringPayment': recurringPayment, 'recurringPayment': recurringPayment?.toJson(),
'expenseCategory': expenseCategory.toJson(), 'expenseCategory': expenseCategory?.toJson(),
'expenseStatus': expenseStatus.toJson(), 'expenseStatus': expenseStatus?.toJson(),
'paidTransactionId': paidTransactionId, 'paidTransactionId': paidTransactionId,
'paidAt': paidAt?.toIso8601String(), 'paidAt': paidAt?.toIso8601String(),
'paidBy': paidBy?.toJson(), 'paidBy': paidBy?.toJson(),
'isAdvancePayment': isAdvancePayment, 'isAdvancePayment': isAdvancePayment,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt?.toIso8601String(),
'createdBy': createdBy.toJson(), 'createdBy': createdBy?.toJson(),
'updatedAt': updatedAt.toIso8601String(), 'updatedAt': updatedAt?.toIso8601String(),
'updatedBy': updatedBy?.toJson(), 'updatedBy': updatedBy?.toJson(),
'nextStatus': nextStatus.map((e) => e.toJson()).toList(), 'nextStatus': nextStatus?.map((e) => e.toJson()).toList(),
'updateLogs': updateLogs.map((e) => e.toJson()).toList(), 'updateLogs': updateLogs?.map((e) => e.toJson()).toList(),
'attachments': attachments.map((e) => e.toJson()).toList(), 'attachments': attachments?.map((e) => e.toJson()).toList(),
'isActive': isActive, 'isActive': isActive,
'isExpenseCreated': isExpenseCreated, 'isExpenseCreated': isExpenseCreated,
}; };
} }
class RecurringPayment { class RecurringPayment {
String id; final String? id;
String recurringPaymentUID; final String? recurringPaymentUID;
double amount; final double? amount;
bool isVariable; final bool? isVariable;
RecurringPayment({ RecurringPayment({
required this.id, this.id,
required this.recurringPaymentUID, this.recurringPaymentUID,
required this.amount, this.amount,
required this.isVariable, this.isVariable,
}); });
factory RecurringPayment.fromJson(Map<String, dynamic> json) => factory RecurringPayment.fromJson(Map<String, dynamic> json) =>
RecurringPayment( RecurringPayment(
id: json['id'], id: json['id'] as String?,
recurringPaymentUID: json['recurringPaymentUID'], recurringPaymentUID: json['recurringPaymentUID'] as String?,
amount: (json['amount'] as num).toDouble(), amount: (json['amount'] as num?)?.toDouble(),
isVariable: json['isVariable'], isVariable: json['isVariable'] as bool?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -207,26 +221,26 @@ class RecurringPayment {
} }
class Currency { class Currency {
String id; final String? id;
String currencyCode; final String? currencyCode;
String currencyName; final String? currencyName;
String symbol; final String? symbol;
bool isActive; final bool? isActive;
Currency({ Currency({
required this.id, this.id,
required this.currencyCode, this.currencyCode,
required this.currencyName, this.currencyName,
required this.symbol, this.symbol,
required this.isActive, this.isActive,
}); });
factory Currency.fromJson(Map<String, dynamic> json) => Currency( factory Currency.fromJson(Map<String, dynamic> json) => Currency(
id: json['id'], id: json['id'] as String?,
currencyCode: json['currencyCode'], currencyCode: json['currencyCode'] as String?,
currencyName: json['currencyName'], currencyName: json['currencyName'] as String?,
symbol: json['symbol'], symbol: json['symbol'] as String?,
isActive: json['isActive'], isActive: json['isActive'] as bool?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -239,39 +253,39 @@ class Currency {
} }
class Project { class Project {
String id; final String? id;
String name; final String? name;
Project({required this.id, required this.name}); Project({this.id, this.name});
factory Project.fromJson(Map<String, dynamic> json) => factory Project.fromJson(Map<String, dynamic> json) =>
Project(id: json['id'], name: json['name']); Project(id: json['id'] as String?, name: json['name'] as String?);
Map<String, dynamic> toJson() => {'id': id, 'name': name}; Map<String, dynamic> toJson() => {'id': id, 'name': name};
} }
class ExpenseCategory { class ExpenseCategory {
String id; final String? id;
String name; final String? name;
bool noOfPersonsRequired; final bool? noOfPersonsRequired;
bool isAttachmentRequried; final bool? isAttachmentRequried;
String description; final String? description;
ExpenseCategory({ ExpenseCategory({
required this.id, this.id,
required this.name, this.name,
required this.noOfPersonsRequired, this.noOfPersonsRequired,
required this.isAttachmentRequried, this.isAttachmentRequried,
required this.description, this.description,
}); });
factory ExpenseCategory.fromJson(Map<String, dynamic> json) => factory ExpenseCategory.fromJson(Map<String, dynamic> json) =>
ExpenseCategory( ExpenseCategory(
id: json['id'], id: json['id'] as String?,
name: json['name'], name: json['name'] as String?,
noOfPersonsRequired: json['noOfPersonsRequired'], noOfPersonsRequired: json['noOfPersonsRequired'] as bool?,
isAttachmentRequried: json['isAttachmentRequried'], isAttachmentRequried: json['isAttachmentRequried'] as bool?,
description: json['description'], description: json['description'] as String?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -284,34 +298,34 @@ class ExpenseCategory {
} }
class ExpenseStatus { class ExpenseStatus {
String id; final String? id;
String name; final String? name;
String displayName; final String? displayName;
String description; final String? description;
List<String>? permissionIds; final List<String>? permissionIds;
String color; final String? color;
bool isSystem; final bool? isSystem;
ExpenseStatus({ ExpenseStatus({
required this.id, this.id,
required this.name, this.name,
required this.displayName, this.displayName,
required this.description, this.description,
this.permissionIds, this.permissionIds,
required this.color, this.color,
required this.isSystem, this.isSystem,
}); });
factory ExpenseStatus.fromJson(Map<String, dynamic> json) => ExpenseStatus( factory ExpenseStatus.fromJson(Map<String, dynamic> json) => ExpenseStatus(
id: json['id'], id: json['id'] as String?,
name: json['name'], name: json['name'] as String?,
displayName: json['displayName'], displayName: json['displayName'] as String?,
description: json['description'], description: json['description'] as String?,
permissionIds: json['permissionIds'] != null permissionIds: json['permissionIds'] != null
? List<String>.from(json['permissionIds']) ? List<String>.from(json['permissionIds'])
: null, : null,
color: json['color'], color: json['color'] as String?,
isSystem: json['isSystem'], isSystem: json['isSystem'] as bool?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -326,32 +340,32 @@ class ExpenseStatus {
} }
class User { class User {
String id; final String? id;
String firstName; final String? firstName;
String lastName; final String? lastName;
String email; final String? email;
String photo; final String? photo;
String jobRoleId; final String? jobRoleId;
String jobRoleName; final String? jobRoleName;
User({ User({
required this.id, this.id,
required this.firstName, this.firstName,
required this.lastName, this.lastName,
required this.email, this.email,
required this.photo, this.photo,
required this.jobRoleId, this.jobRoleId,
required this.jobRoleName, this.jobRoleName,
}); });
factory User.fromJson(Map<String, dynamic> json) => User( factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'], id: json['id'] as String?,
firstName: json['firstName'], firstName: json['firstName'] as String?,
lastName: json['lastName'], lastName: json['lastName'] as String?,
email: json['email'], email: json['email'] as String?,
photo: json['photo'], photo: json['photo'] as String?,
jobRoleId: json['jobRoleId'], jobRoleId: json['jobRoleId'] as String?,
jobRoleName: json['jobRoleName'], jobRoleName: json['jobRoleName'] as String?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -366,34 +380,34 @@ class User {
} }
class NextStatus { class NextStatus {
String id; final String? id;
String name; final String? name;
String displayName; final String? displayName;
String description; final String? description;
List<String>? permissionIds; final List<String>? permissionIds;
String color; final String? color;
bool isSystem; final bool? isSystem;
NextStatus({ NextStatus({
required this.id, this.id,
required this.name, this.name,
required this.displayName, this.displayName,
required this.description, this.description,
this.permissionIds, this.permissionIds,
required this.color, this.color,
required this.isSystem, this.isSystem,
}); });
factory NextStatus.fromJson(Map<String, dynamic> json) => NextStatus( factory NextStatus.fromJson(Map<String, dynamic> json) => NextStatus(
id: json['id'], id: json['id'] as String?,
name: json['name'], name: json['name'] as String?,
displayName: json['displayName'], displayName: json['displayName'] as String?,
description: json['description'], description: json['description'] as String?,
permissionIds: json['permissionIds'] != null permissionIds: json['permissionIds'] != null
? List<String>.from(json['permissionIds']) ? List<String>.from(json['permissionIds'])
: null, : null,
color: json['color'], color: json['color'] as String?,
isSystem: json['isSystem'], isSystem: json['isSystem'] as bool?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -408,67 +422,73 @@ class NextStatus {
} }
class UpdateLog { class UpdateLog {
String id; final String? id;
ExpenseStatus? status; final ExpenseStatus? status;
ExpenseStatus nextStatus; final ExpenseStatus? nextStatus;
String comment; final String? comment;
DateTime updatedAt; final DateTime? updatedAt;
User updatedBy; final User? updatedBy;
UpdateLog({ UpdateLog({
required this.id, this.id,
this.status, this.status,
required this.nextStatus, this.nextStatus,
required this.comment, this.comment,
required this.updatedAt, this.updatedAt,
required this.updatedBy, this.updatedBy,
}); });
factory UpdateLog.fromJson(Map<String, dynamic> json) => UpdateLog( factory UpdateLog.fromJson(Map<String, dynamic> json) => UpdateLog(
id: json['id'], id: json['id'] as String?,
status: json['status'] != null status: json['status'] != null
? ExpenseStatus.fromJson(json['status']) ? ExpenseStatus.fromJson(json['status'])
: null, : null,
nextStatus: ExpenseStatus.fromJson(json['nextStatus']), nextStatus: json['nextStatus'] != null
comment: json['comment'], ? ExpenseStatus.fromJson(json['nextStatus'])
updatedAt: DateTime.parse(json['updatedAt']), : null,
updatedBy: User.fromJson(json['updatedBy']), comment: json['comment'] as String?,
updatedAt: json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'])
: null,
updatedBy: json['updatedBy'] != null
? User.fromJson(json['updatedBy'])
: null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'status': status?.toJson(), 'status': status?.toJson(),
'nextStatus': nextStatus.toJson(), 'nextStatus': nextStatus?.toJson(),
'comment': comment, 'comment': comment,
'updatedAt': updatedAt.toIso8601String(), 'updatedAt': updatedAt?.toIso8601String(),
'updatedBy': updatedBy.toJson(), 'updatedBy': updatedBy?.toJson(),
}; };
} }
class Attachment { class Attachment {
String id; final String? id;
String fileName; final String? fileName;
String url; final String? url;
String? thumbUrl; final String? thumbUrl;
int fileSize; final int? fileSize;
String contentType; final String? contentType;
Attachment({ Attachment({
required this.id, this.id,
required this.fileName, this.fileName,
required this.url, this.url,
this.thumbUrl, this.thumbUrl,
required this.fileSize, this.fileSize,
required this.contentType, this.contentType,
}); });
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment( factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
id: json['id'], id: json['id'] as String?,
fileName: json['fileName'], fileName: json['fileName'] as String?,
url: json['url'], url: json['url'] as String?,
thumbUrl: json['thumbUrl'], thumbUrl: json['thumbUrl'] as String?,
fileSize: json['fileSize'], fileSize: json['fileSize'] as int?,
contentType: json['contentType'], contentType: json['contentType'] as String?,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

View File

@ -50,9 +50,9 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
void _checkPermissionToSubmit(PaymentRequestData request) { void _checkPermissionToSubmit(PaymentRequestData request) {
const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7'; const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
final isCreatedByCurrentUser = employeeInfo?.id == request.createdBy.id; final isCreatedByCurrentUser = employeeInfo?.id == request.createdBy?.id;
final hasDraftNextStatus = final hasDraftNextStatus =
request.nextStatus.any((s) => s.id == draftStatusId); (request.nextStatus ?? []).any((s) => (s.id ?? '') == draftStatusId);
canSubmit.value = isCreatedByCurrentUser && hasDraftNextStatus; canSubmit.value = isCreatedByCurrentUser && hasDraftNextStatus;
} }
@ -61,36 +61,41 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
setState(() {}); setState(() {});
} }
Color _parseColor(String hexColor) { Color _parseColor(String? hexColor) {
String hex = hexColor.toUpperCase().replaceAll('#', ''); try {
if (hex.length == 6) hex = 'FF$hex'; if (hexColor == null || hexColor.trim().isEmpty) return Colors.grey;
return Color(int.parse(hex, radix: 16)); String hex = hexColor.toUpperCase().replaceAll('#', '');
if (hex.length == 6) hex = 'FF$hex';
return Color(int.parse(hex, radix: 16));
} catch (_) {
return Colors.grey;
}
} }
void _openEditPaymentRequestBottomSheet(request) { void _openEditPaymentRequestBottomSheet(PaymentRequestData request) {
showPaymentRequestBottomSheet( showPaymentRequestBottomSheet(
isEdit: true, isEdit: true,
existingData: { existingData: {
"id": request.id, "id": request.id ?? '',
"paymentRequestId": request.paymentRequestUID, "paymentRequestId": request.paymentRequestUID ?? '',
"title": request.title, "title": request.title ?? '',
"projectId": request.project.id, "projectId": request.project?.id ?? '',
"projectName": request.project.name, "projectName": request.project?.name ?? '',
"expenseCategoryId": request.expenseCategory.id, "expenseCategoryId": request.expenseCategory?.id ?? '',
"expenseCategoryName": request.expenseCategory.name, "expenseCategoryName": request.expenseCategory?.name ?? '',
"amount": request.amount.toString(), "amount": (request.amount ?? 0).toString(),
"currencyId": request.currency.id, "currencyId": request.currency?.id ?? '',
"currencySymbol": request.currency.symbol, "currencySymbol": request.currency?.symbol ?? '',
"payee": request.payee, "payee": request.payee ?? '',
"description": request.description, "description": request.description ?? '',
"isAdvancePayment": request.isAdvancePayment, "isAdvancePayment": request.isAdvancePayment ?? false,
"dueDate": request.dueDate, "dueDate": request.dueDate?.toIso8601String() ?? '',
"attachments": request.attachments "attachments": (request.attachments ?? [])
.map((a) => { .map((a) => {
"url": a.url, "url": a.url ?? '',
"fileName": a.fileName, "fileName": a.fileName ?? '',
"documentId": a.id, "documentId": a.id ?? '',
"contentType": a.contentType, "contentType": a.contentType ?? '',
}) })
.toList(), .toList(),
}, },
@ -105,70 +110,72 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: _buildAppBar(), appBar: _buildAppBar(),
body: SafeArea(child: Obx(() { body: SafeArea(
if (controller.isLoading.value && child: Obx(() {
controller.paymentRequest.value == null) { if (controller.isLoading.value &&
return SkeletonLoaders.paymentRequestDetailSkeletonLoader(); controller.paymentRequest.value == null) {
} return SkeletonLoaders.paymentRequestDetailSkeletonLoader();
}
final request = controller.paymentRequest.value; final request = controller.paymentRequest.value;
if (controller.errorMessage.isNotEmpty) { if ((controller.errorMessage.value ).isNotEmpty) {
return Center( return Center(child: MyText.bodyMedium(controller.errorMessage.value));
child: MyText.bodyMedium(controller.errorMessage.value)); }
}
if (request == null) { if (request == null) {
return Center(child: MyText.bodyMedium("No data to display.")); return Center(child: MyText.bodyMedium("No data to display."));
} }
return MyRefreshIndicator( return MyRefreshIndicator(
onRefresh: controller.fetchPaymentRequestDetail, onRefresh: controller.fetchPaymentRequestDetail,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
12, 12,
12, 12,
12, 12,
60 + MediaQuery.of(context).padding.bottom, 60 + MediaQuery.of(context).padding.bottom,
), ),
child: Center( child: Center(
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 520), constraints: const BoxConstraints(maxWidth: 520),
child: Card( child: Card(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)), borderRadius: BorderRadius.circular(5)),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 14), vertical: 12, horizontal: 14),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_Header( _Header(
request: request, request: request,
colorParser: _parseColor, colorParser: _parseColor,
employeeInfo: employeeInfo, employeeInfo: employeeInfo,
onEdit: () => onEdit: () => _openEditPaymentRequestBottomSheet(request),
_openEditPaymentRequestBottomSheet(request), ),
), const Divider(height: 30, thickness: 1.2),
const Divider(height: 30, thickness: 1.2), _Logs(
_Logs( logs: request.updateLogs ?? [],
logs: request.updateLogs, colorParser: _parseColor), colorParser: _parseColor,
const Divider(height: 30, thickness: 1.2), ),
_Parties(request: request), const Divider(height: 30, thickness: 1.2),
const Divider(height: 30, thickness: 1.2), _Parties(request: request),
_DetailsTable(request: request), const Divider(height: 30, thickness: 1.2),
const Divider(height: 30, thickness: 1.2), _DetailsTable(request: request),
_Documents(documents: request.attachments), const Divider(height: 30, thickness: 1.2),
MySpacing.height(24), _Documents(documents: request.attachments ?? []),
], MySpacing.height(24),
],
),
), ),
), ),
), ),
), ),
), ),
), );
); }),
})), ),
bottomNavigationBar: _buildBottomActionBar(), bottomNavigationBar: _buildBottomActionBar(),
); );
} }
@ -192,14 +199,18 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
const reimbursementStatusId = '61578360-3a49-4c34-8604-7b35a3787b95'; const reimbursementStatusId = '61578360-3a49-4c34-8604-7b35a3787b95';
const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7'; const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
final availableStatuses = request.nextStatus.where((status) { final availableStatuses = (request.nextStatus ?? []).where((status) {
if (status.id == draftStatusId) { if ((status.id ?? '') == draftStatusId) {
return employeeInfo?.id == request.createdBy.id; return employeeInfo?.id == request.createdBy?.id;
} }
return permissionController return permissionController
.hasAnyPermission(status.permissionIds ?? []); .hasAnyPermission(status.permissionIds ?? []);
}).toList(); }).toList();
if (availableStatuses.isEmpty) {
return const SizedBox.shrink();
}
return SafeArea( return SafeArea(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -224,7 +235,10 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
), ),
), ),
onPressed: () async { onPressed: () async {
if (status.id == reimbursementStatusId) { final statusId = status.id ?? '';
final dispName = status.displayName ?? '';
if (statusId == reimbursementStatusId) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -233,23 +247,23 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
BorderRadius.vertical(top: Radius.circular(5)), BorderRadius.vertical(top: Radius.circular(5)),
), ),
builder: (ctx) => UpdatePaymentRequestWithReimbursement( builder: (ctx) => UpdatePaymentRequestWithReimbursement(
expenseId: request.paymentRequestUID, expenseId: request.paymentRequestUID ?? '',
statusId: status.id, statusId: statusId,
onClose: () {}, onClose: () {},
), ),
); );
} else if (status.id == } else if (statusId ==
'b8586f67-dc19-49c3-b4af-224149efe1d3') { 'b8586f67-dc19-49c3-b4af-224149efe1d3') {
showCreateExpenseBottomSheet( showCreateExpenseBottomSheet(
statusId: status.id, statusId: statusId,
); );
} else { } else {
final comment = await showCommentBottomSheet( final comment =
context, status.displayName); await showCommentBottomSheet(context, dispName);
if (comment == null || comment.trim().isEmpty) return; if (comment == null || comment.trim().isEmpty) return;
final success = await controller.updatePaymentRequestStatus( final success = await controller.updatePaymentRequestStatus(
statusId: status.id, statusId: statusId,
comment: comment.trim(), comment: comment.trim(),
); );
@ -258,14 +272,15 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
message: success message: success
? 'Status updated successfully' ? 'Status updated successfully'
: 'Failed to update status', : 'Failed to update status',
type: success ? SnackbarType.success : SnackbarType.error, type:
success ? SnackbarType.success : SnackbarType.error,
); );
if (success) await controller.fetchPaymentRequestDetail(); if (success) await controller.fetchPaymentRequestDetail();
} }
}, },
child: MyText.bodySmall( child: MyText.bodySmall(
status.displayName, status.displayName ?? '',
color: Colors.white, color: Colors.white,
fontWeight: 600, fontWeight: 600,
), ),
@ -308,8 +323,8 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
), ),
MySpacing.height(2), MySpacing.height(2),
GetBuilder<ProjectController>(builder: (_) { GetBuilder<ProjectController>(builder: (_) {
final name = projectController.selectedProject?.name ?? final name =
'Select Project'; projectController.selectedProject?.name ?? 'Select Project';
return Row( return Row(
children: [ children: [
const Icon(Icons.work_outline, const Icon(Icons.work_outline,
@ -340,14 +355,14 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
class PaymentRequestPermissionHelper { class PaymentRequestPermissionHelper {
static bool canEditPaymentRequest( static bool canEditPaymentRequest(
EmployeeInfo? employee, PaymentRequestData request) { EmployeeInfo? employee, PaymentRequestData request) {
return employee?.id == request.createdBy.id && return employee?.id == request.createdBy?.id &&
_isInAllowedEditStatus(request.expenseStatus.id); _isInAllowedEditStatus(request.expenseStatus?.id ?? '');
} }
static bool canSubmitPaymentRequest( static bool canSubmitPaymentRequest(
EmployeeInfo? employee, PaymentRequestData request) { EmployeeInfo? employee, PaymentRequestData request) {
return employee?.id == request.createdBy.id && return employee?.id == request.createdBy?.id &&
request.nextStatus.isNotEmpty; (request.nextStatus ?? []).isNotEmpty;
} }
static bool _isInAllowedEditStatus(String statusId) { static bool _isInAllowedEditStatus(String statusId) {
@ -362,9 +377,10 @@ class PaymentRequestPermissionHelper {
// ------------------ Sub-widgets ------------------ // ------------------ Sub-widgets ------------------
// Header widget
class _Header extends StatefulWidget { class _Header extends StatefulWidget {
final PaymentRequestData request; final PaymentRequestData request;
final Color Function(String) colorParser; final Color Function(String?) colorParser;
final VoidCallback? onEdit; final VoidCallback? onEdit;
final EmployeeInfo? employeeInfo; final EmployeeInfo? employeeInfo;
@ -382,7 +398,7 @@ class _Header extends StatefulWidget {
class _HeaderState extends State<_Header> with UIMixin { class _HeaderState extends State<_Header> with UIMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final statusColor = widget.colorParser(widget.request.expenseStatus.color); final statusColor = widget.colorParser(widget.request.expenseStatus?.color);
final canEdit = widget.employeeInfo != null && final canEdit = widget.employeeInfo != null &&
PaymentRequestPermissionHelper.canEditPaymentRequest( PaymentRequestPermissionHelper.canEditPaymentRequest(
@ -395,7 +411,7 @@ class _HeaderState extends State<_Header> with UIMixin {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
MyText.bodyMedium( MyText.bodyMedium(
'ID: ${widget.request.paymentRequestUID}', 'ID: ${widget.request.paymentRequestUID ?? '-'}',
fontWeight: 700, fontWeight: 700,
fontSize: 14, fontSize: 14,
), ),
@ -422,7 +438,7 @@ class _HeaderState extends State<_Header> with UIMixin {
Expanded( Expanded(
child: MyText.bodySmall( child: MyText.bodySmall(
DateTimeUtils.convertUtcToLocal( DateTimeUtils.convertUtcToLocal(
widget.request.createdAt.toIso8601String(), widget.request.createdAt?.toIso8601String() ?? '',
format: 'dd MMM yyyy'), format: 'dd MMM yyyy'),
fontWeight: 600, fontWeight: 600,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -443,7 +459,7 @@ class _HeaderState extends State<_Header> with UIMixin {
SizedBox( SizedBox(
width: 100, width: 100,
child: MyText.labelSmall( child: MyText.labelSmall(
widget.request.expenseStatus.displayName, widget.request.expenseStatus?.displayName ?? '-',
color: statusColor, color: statusColor,
fontWeight: 600, fontWeight: 600,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -459,11 +475,10 @@ class _HeaderState extends State<_Header> with UIMixin {
} }
} }
// ------------------ Logs, Parties, Details, Documents ------------------ // Logs widget
class _Logs extends StatelessWidget { class _Logs extends StatelessWidget {
final List<UpdateLog> logs; final List<UpdateLog> logs;
final Color Function(String) colorParser; final Color Function(String?) colorParser;
const _Logs({required this.logs, required this.colorParser}); const _Logs({required this.logs, required this.colorParser});
@override @override
@ -490,21 +505,25 @@ class _Logs extends StatelessWidget {
? colorParser(log.status!.color) ? colorParser(log.status!.color)
: Colors.grey; : Colors.grey;
final comment = log.comment; final comment = log.comment ?? '';
final nextStatusName = log.nextStatus.name; final nextStatusName = log.nextStatus?.name ?? '';
final updatedBy = log.updatedBy; final updatedBy = log.updatedBy;
final first = updatedBy?.firstName ?? '';
final last = updatedBy?.lastName ?? '';
final initials = final initials =
'${updatedBy.firstName.isNotEmpty == true ? updatedBy.firstName[0] : ''}' '${first.isNotEmpty ? first[0] : ''}${last.isNotEmpty ? last[0] : ''}';
'${updatedBy.lastName.isNotEmpty == true ? updatedBy.lastName[0] : ''}'; final name = ((first + ' ' + last).trim().isNotEmpty)
final name = '${updatedBy.firstName} ${updatedBy.lastName}'; ? '$first $last'
: '-';
final timestamp = log.updatedAt final updatedAt = log.updatedAt;
.toUtc() final timeAgo = (updatedAt != null)
.add(const Duration(hours: 5, minutes: 30)); ? timeago.format(updatedAt.toUtc().add(const Duration(hours: 5, minutes: 30)))
final timeAgo = timeago.format(timestamp); : '-';
final nextStatusColor = colorParser(log.nextStatus.color); final nextStatusColor =
colorParser(log.nextStatus?.color ?? '');
return TimelineTile( return TimelineTile(
alignment: TimelineAlign.start, alignment: TimelineAlign.start,
@ -583,6 +602,7 @@ class _Logs extends StatelessWidget {
} }
} }
// Parties widget
class _Parties extends StatelessWidget { class _Parties extends StatelessWidget {
final PaymentRequestData request; final PaymentRequestData request;
const _Parties({required this.request}); const _Parties({required this.request});
@ -602,7 +622,7 @@ class _Parties extends StatelessWidget {
children: [ children: [
MyText.labelMedium("Payee", fontWeight: 600), MyText.labelMedium("Payee", fontWeight: 600),
MySpacing.height(2), MySpacing.height(2),
MyText.bodyMedium(request.payee), MyText.bodyMedium(request.payee ?? '-'),
], ],
), ),
), ),
@ -612,7 +632,7 @@ class _Parties extends StatelessWidget {
children: [ children: [
MyText.labelMedium("Project", fontWeight: 600), MyText.labelMedium("Project", fontWeight: 600),
MySpacing.height(2), MySpacing.height(2),
MyText.bodyMedium(request.project.name), MyText.bodyMedium(request.project?.name ?? '-'),
], ],
), ),
), ),
@ -623,98 +643,105 @@ class _Parties extends StatelessWidget {
} }
} }
// Details table widget
class _DetailsTable extends StatelessWidget { class _DetailsTable extends StatelessWidget {
final PaymentRequestData request; final PaymentRequestData request;
const _DetailsTable({required this.request}); const _DetailsTable({required this.request});
String _formatCurrencyAmount(String? symbol, double? amount) {
final sym = symbol ?? '';
final amt = amount ?? 0.0;
return '$sym ${amt.toStringAsFixed(2)}';
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currencySymbol = request.currency?.symbol ?? '';
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Basic Info // Basic Info
_labelValueRow("Payment Request ID:", request.paymentRequestUID), _labelValueRow("Payment Request ID:", request.paymentRequestUID ?? '-'),
if (request.paidTransactionId != null && if ((request.paidTransactionId ?? '').isNotEmpty)
request.paidTransactionId!.isNotEmpty) _labelValueRow("Transaction ID:", request.paidTransactionId ?? ''),
_labelValueRow("Transaction ID:", request.paidTransactionId!), _labelValueRow("Payee:", request.payee ?? '-'),
_labelValueRow("Payee:", request.payee), _labelValueRow("Project:", request.project?.name ?? '-'),
_labelValueRow("Project:", request.project.name), _labelValueRow("Expense Category:", request.expenseCategory?.name ?? '-'),
_labelValueRow("Expense Category:", request.expenseCategory.name),
// Amounts // Amounts
_labelValueRow("Amount:", _labelValueRow("Amount:", _formatCurrencyAmount(currencySymbol, request.amount)),
"${request.currency.symbol} ${request.amount.toStringAsFixed(2)}"),
if (request.baseAmount != null) if (request.baseAmount != null)
_labelValueRow("Base Amount:", _labelValueRow("Base Amount:", _formatCurrencyAmount(currencySymbol, request.baseAmount)),
"${request.currency.symbol} ${request.baseAmount!.toStringAsFixed(2)}"),
if (request.taxAmount != null) if (request.taxAmount != null)
_labelValueRow("Tax Amount:", _labelValueRow("Tax Amount:", _formatCurrencyAmount(currencySymbol, request.taxAmount)),
"${request.currency.symbol} ${request.taxAmount!.toStringAsFixed(2)}"), if (request.expenseCategory?.noOfPersonsRequired == true)
if (request.expenseCategory.noOfPersonsRequired)
_labelValueRow("Additional Persons Required:", "Yes"), _labelValueRow("Additional Persons Required:", "Yes"),
if (request.expenseCategory.isAttachmentRequried) if (request.expenseCategory?.isAttachmentRequried == true)
_labelValueRow("Attachment Required:", "Yes"), _labelValueRow("Attachment Required:", "Yes"),
// Dates // Dates
_labelValueRow( _labelValueRow(
"Due Date:", "Due Date:",
DateTimeUtils.convertUtcToLocal(request.dueDate.toIso8601String(), DateTimeUtils.convertUtcToLocal(
request.dueDate?.toIso8601String() ?? '',
format: 'dd MMM yyyy')), format: 'dd MMM yyyy')),
_labelValueRow( _labelValueRow(
"Created At:", "Created At:",
DateTimeUtils.convertUtcToLocal(request.createdAt.toIso8601String(), DateTimeUtils.convertUtcToLocal(
request.createdAt?.toIso8601String() ?? '',
format: 'dd MMM yyyy')), format: 'dd MMM yyyy')),
_labelValueRow( _labelValueRow(
"Updated At:", "Updated At:",
DateTimeUtils.convertUtcToLocal(request.updatedAt.toIso8601String(), DateTimeUtils.convertUtcToLocal(
request.updatedAt?.toIso8601String() ?? '',
format: 'dd MMM yyyy')), format: 'dd MMM yyyy')),
// Payment Info // Payment Info
if (request.paidAt != null) if (request.paidAt != null)
_labelValueRow( _labelValueRow(
"Transaction Date:", "Transaction Date:",
DateTimeUtils.convertUtcToLocal(request.paidAt!.toIso8601String(), DateTimeUtils.convertUtcToLocal(
format: 'dd MMM yyyy')), request.paidAt?.toIso8601String() ?? '',
format: 'dd MMM yyyy'),
),
if (request.paidBy != null) if (request.paidBy != null)
_labelValueRow("Paid By:", _labelValueRow("Paid By:", "${request.paidBy?.firstName ?? ''} ${request.paidBy?.lastName ?? ''}".trim()),
"${request.paidBy!.firstName} ${request.paidBy!.lastName}"),
// Flags // Flags
_labelValueRow( _labelValueRow(
"Advance Payment:", request.isAdvancePayment ? "Yes" : "No"), "Advance Payment:", (request.isAdvancePayment ?? false) ? "Yes" : "No"),
_labelValueRow( _labelValueRow(
"Expense Created:", request.isExpenseCreated ? "Yes" : "No"), "Expense Created:", (request.isExpenseCreated ?? false) ? "Yes" : "No"),
_labelValueRow("Active:", request.isActive ? "Yes" : "No"), _labelValueRow("Active:", (request.isActive ?? false) ? "Yes" : "No"),
// Recurring Payment Info // Recurring Payment Info
if (request.recurringPayment != null) ...[ if (request.recurringPayment != null) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
MyText.bodySmall("Recurring Payment Info:", fontWeight: 600), MyText.bodySmall("Recurring Payment Info:", fontWeight: 600),
_labelValueRow( _labelValueRow("Recurring ID:", request.recurringPayment?.recurringPaymentUID ?? '-'),
"Recurring ID:", request.recurringPayment!.recurringPaymentUID), _labelValueRow("Amount:", _formatCurrencyAmount(currencySymbol, request.recurringPayment?.amount)),
_labelValueRow("Amount:", _labelValueRow("Variable Amount:", (request.recurringPayment?.isVariable ?? false) ? "Yes" : "No"),
"${request.currency.symbol} ${request.recurringPayment!.amount.toStringAsFixed(2)}"),
_labelValueRow("Variable Amount:",
request.recurringPayment!.isVariable ? "Yes" : "No"),
], ],
// Description & Attachments // Description & Attachments
_labelValueRow("Description:", request.description), _labelValueRow("Description:", request.description ?? '-'),
_labelValueRow( _labelValueRow("Attachment:", (request.attachments ?? []).isNotEmpty ? "Yes" : "No"),
"Attachment:", request.attachments.isNotEmpty ? "Yes" : "No"),
], ],
); );
} }
} }
// Documents widget
class _Documents extends StatelessWidget { class _Documents extends StatelessWidget {
final List<Attachment> documents; final List<Attachment> documents;
const _Documents({required this.documents}); const _Documents({required this.documents});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (documents.isEmpty) if (documents.isEmpty) {
return MyText.bodyMedium('No Documents', color: Colors.grey); return MyText.bodyMedium('No Documents', color: Colors.grey);
}
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -728,26 +755,36 @@ class _Documents extends StatelessWidget {
separatorBuilder: (_, __) => const SizedBox(height: 8), separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final doc = documents[index]; final doc = documents[index];
final isImage = doc.contentType.startsWith('image/'); final contentType = doc.contentType ?? '';
final isImage = contentType.startsWith('image/');
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
final imageDocs = documents final imageDocs = documents
.where((d) => d.contentType.startsWith('image/')) .where((d) => (d.contentType ?? '').startsWith('image/'))
.toList(); .toList();
final initialIndex = final initialIndex =
imageDocs.indexWhere((d) => d.id == doc.id); imageDocs.indexWhere((d) => (d.id ?? '') == (doc.id ?? ''));
if (isImage && imageDocs.isNotEmpty && initialIndex != -1) { if (isImage && imageDocs.isNotEmpty && initialIndex != -1) {
showDialog( showDialog(
context: context, context: context,
builder: (_) => ImageViewerDialog( builder: (_) => ImageViewerDialog(
imageSources: imageDocs.map((e) => e.url).toList(), imageSources: imageDocs.map((e) => e.url ?? '').toList(),
initialIndex: initialIndex, initialIndex: initialIndex,
), ),
); );
} else { } else {
final Uri url = Uri.parse(doc.url); final urlStr = doc.url ?? '';
if (urlStr.isEmpty) {
showAppSnackbar(
title: 'Error',
message: 'Document URL is missing.',
type: SnackbarType.error,
);
return;
}
final Uri url = Uri.parse(urlStr);
if (await canLaunchUrl(url)) { if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication); await launchUrl(url, mode: LaunchMode.externalApplication);
} else { } else {
@ -774,7 +811,7 @@ class _Documents extends StatelessWidget {
const SizedBox(width: 7), const SizedBox(width: 7),
Expanded( Expanded(
child: MyText.bodySmall( child: MyText.bodySmall(
doc.fileName, doc.fileName ?? (doc.url ?? '-'),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),