Merge remote-tracking branch 'origin/master' into ambulance_listing_and_request
# Conflicts: # lib/core/dependencies.dart # lib/features/emergency_services/emergency_services_view_model.dart # lib/presentation/appointments/widgets/appointment_doctor_card.dart # lib/widgets/common_bottom_sheet.dartpull/96/head
commit
53be50b40c
@ -0,0 +1,115 @@
|
||||
// Dart model for the "AncillaryOrderList" structure
|
||||
// Uses DateUtil.convertStringToDate and DateUtil.dateToDotNetString from your project to parse/serialize .NET-style dates.
|
||||
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
|
||||
|
||||
class AncillaryOrderListModel {
|
||||
List<AncillaryOrderGroup>? ancillaryOrderList;
|
||||
|
||||
AncillaryOrderListModel({this.ancillaryOrderList});
|
||||
|
||||
factory AncillaryOrderListModel.fromJson(Map<String, dynamic> json) => AncillaryOrderListModel(
|
||||
ancillaryOrderList: json['AncillaryOrderList'] != null
|
||||
? List<AncillaryOrderGroup>.from(
|
||||
(json['AncillaryOrderList'] as List).map(
|
||||
(x) => AncillaryOrderGroup.fromJson(x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
class AncillaryOrderGroup {
|
||||
List<AncillaryOrderItem>? ancillaryOrderList;
|
||||
dynamic errCode;
|
||||
String? message;
|
||||
int? patientID;
|
||||
String? patientName;
|
||||
int? patientType;
|
||||
int? projectID;
|
||||
String? projectName;
|
||||
String? setupID;
|
||||
int? statusCode;
|
||||
|
||||
AncillaryOrderGroup({
|
||||
this.ancillaryOrderList,
|
||||
this.errCode,
|
||||
this.message,
|
||||
this.patientID,
|
||||
this.patientName,
|
||||
this.patientType,
|
||||
this.projectID,
|
||||
this.projectName,
|
||||
this.setupID,
|
||||
this.statusCode,
|
||||
});
|
||||
|
||||
factory AncillaryOrderGroup.fromJson(Map<String, dynamic> json) => AncillaryOrderGroup(
|
||||
ancillaryOrderList: json['AncillaryOrderList'] != null
|
||||
? List<AncillaryOrderItem>.from(
|
||||
(json['AncillaryOrderList'] as List).map(
|
||||
(x) => AncillaryOrderItem.fromJson(x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
errCode: json['ErrCode'],
|
||||
message: json['Message'] as String?,
|
||||
patientID: json['PatientID'] as int?,
|
||||
patientName: json['PatientName'] as String?,
|
||||
patientType: json['PatientType'] as int?,
|
||||
projectID: json['ProjectID'] as int?,
|
||||
projectName: json['ProjectName'] as String?,
|
||||
setupID: json['SetupID'] as String?,
|
||||
statusCode: json['StatusCode'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
class AncillaryOrderItem {
|
||||
dynamic ancillaryProcedureListModels;
|
||||
DateTime? appointmentDate;
|
||||
int? appointmentNo;
|
||||
int? clinicID;
|
||||
String? clinicName;
|
||||
int? doctorID;
|
||||
String? doctorName;
|
||||
int? invoiceNo;
|
||||
bool? isCheckInAllow;
|
||||
bool? isQueued;
|
||||
DateTime? orderDate;
|
||||
int? orderNo;
|
||||
String? projectName; // Added from parent AncillaryOrderGroup
|
||||
int? projectID; // Added from parent AncillaryOrderGroup
|
||||
|
||||
AncillaryOrderItem({
|
||||
this.ancillaryProcedureListModels,
|
||||
this.appointmentDate,
|
||||
this.appointmentNo,
|
||||
this.clinicID,
|
||||
this.clinicName,
|
||||
this.doctorID,
|
||||
this.doctorName,
|
||||
this.invoiceNo,
|
||||
this.isCheckInAllow,
|
||||
this.isQueued,
|
||||
this.orderDate,
|
||||
this.orderNo,
|
||||
this.projectName,
|
||||
this.projectID,
|
||||
});
|
||||
|
||||
factory AncillaryOrderItem.fromJson(Map<String, dynamic> json, {String? projectName, int? projectID}) => AncillaryOrderItem(
|
||||
ancillaryProcedureListModels: json['AncillaryProcedureListModels'],
|
||||
appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']),
|
||||
appointmentNo: json['AppointmentNo'] as int?,
|
||||
clinicID: json['ClinicID'] as int?,
|
||||
clinicName: json['ClinicName'] as String?,
|
||||
doctorID: json['DoctorID'] as int?,
|
||||
doctorName: json['DoctorName'] as String?,
|
||||
invoiceNo: json['Invoiceno'] as int?,
|
||||
isCheckInAllow: json['IsCheckInAllow'] as bool?,
|
||||
isQueued: json['IsQueued'] as bool?,
|
||||
orderDate: DateUtil.convertStringToDate(json['OrderDate']),
|
||||
orderNo: json['OrderNo'] as int?,
|
||||
projectName: projectName,
|
||||
projectID: projectID,
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,221 @@
|
||||
// Dart model classes for "AncillaryOrderProcList"
|
||||
// Generated for user: faizatflutter
|
||||
// Uses DateUtil.convertStringToDate for parsing .NET-style dates (same approach as your PatientRadiologyResponseModel)
|
||||
|
||||
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
|
||||
|
||||
class AncillaryOrderProcListModel {
|
||||
List<AncillaryOrderProcedureItem>? ancillaryOrderProcList;
|
||||
|
||||
AncillaryOrderProcListModel({this.ancillaryOrderProcList});
|
||||
|
||||
factory AncillaryOrderProcListModel.fromJson(Map<String, dynamic> json) => AncillaryOrderProcListModel(
|
||||
ancillaryOrderProcList: json['AncillaryOrderProcList'] != null
|
||||
? List<AncillaryOrderProcedureItem>.from(
|
||||
(json['AncillaryOrderProcList'] as List).map(
|
||||
(x) => AncillaryOrderProcedureItem.fromJson(x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
class AncillaryOrderProcedureItem {
|
||||
List<AncillaryOrderProcDetail>? ancillaryOrderProcDetailsList;
|
||||
DateTime? appointmentDate;
|
||||
int? appointmentNo;
|
||||
int? clinicID;
|
||||
String? clinicName;
|
||||
int? companyID;
|
||||
String? companyName;
|
||||
int? doctorID;
|
||||
String? doctorName;
|
||||
dynamic errCode;
|
||||
int? groupID;
|
||||
String? insurancePolicyNo;
|
||||
String? message;
|
||||
String? patientCardID;
|
||||
int? patientID;
|
||||
String? patientName;
|
||||
int? patientType;
|
||||
int? policyID;
|
||||
String? policyName;
|
||||
int? projectID;
|
||||
String? setupID;
|
||||
int? statusCode;
|
||||
int? subCategoryID;
|
||||
String? subPolicyNo;
|
||||
|
||||
AncillaryOrderProcedureItem({
|
||||
this.ancillaryOrderProcDetailsList,
|
||||
this.appointmentDate,
|
||||
this.appointmentNo,
|
||||
this.clinicID,
|
||||
this.clinicName,
|
||||
this.companyID,
|
||||
this.companyName,
|
||||
this.doctorID,
|
||||
this.doctorName,
|
||||
this.errCode,
|
||||
this.groupID,
|
||||
this.insurancePolicyNo,
|
||||
this.message,
|
||||
this.patientCardID,
|
||||
this.patientID,
|
||||
this.patientName,
|
||||
this.patientType,
|
||||
this.policyID,
|
||||
this.policyName,
|
||||
this.projectID,
|
||||
this.setupID,
|
||||
this.statusCode,
|
||||
this.subCategoryID,
|
||||
this.subPolicyNo,
|
||||
});
|
||||
|
||||
factory AncillaryOrderProcedureItem.fromJson(Map<String, dynamic> json) => AncillaryOrderProcedureItem(
|
||||
ancillaryOrderProcDetailsList: json['AncillaryOrderProcDetailsList'] != null
|
||||
? List<AncillaryOrderProcDetail>.from(
|
||||
(json['AncillaryOrderProcDetailsList'] as List).map(
|
||||
(x) => AncillaryOrderProcDetail.fromJson(x as Map<String, dynamic>),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']),
|
||||
appointmentNo: json['AppointmentNo'] as int?,
|
||||
clinicID: json['ClinicID'] as int?,
|
||||
clinicName: json['ClinicName'] as String?,
|
||||
companyID: json['CompanyID'] as int?,
|
||||
companyName: json['CompanyName'] as String?,
|
||||
doctorID: json['DoctorID'] as int?,
|
||||
doctorName: json['DoctorName'] as String?,
|
||||
errCode: json['ErrCode'],
|
||||
groupID: json['GroupID'] as int?,
|
||||
insurancePolicyNo: json['InsurancePolicyNo'] as String?,
|
||||
message: json['Message'] as String?,
|
||||
patientCardID: json['PatientCardID'] as String?,
|
||||
patientID: json['PatientID'] as int?,
|
||||
patientName: json['PatientName'] as String?,
|
||||
patientType: json['PatientType'] as int?,
|
||||
policyID: json['PolicyID'] as int?,
|
||||
policyName: json['PolicyName'] as String?,
|
||||
projectID: json['ProjectID'] as int?,
|
||||
setupID: json['SetupID'] as String?,
|
||||
statusCode: json['StatusCode'] as int?,
|
||||
subCategoryID: json['SubCategoryID'] as int?,
|
||||
subPolicyNo: json['SubPolicyNo'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class AncillaryOrderProcDetail {
|
||||
int? approvalLineItemNo;
|
||||
int? approvalNo;
|
||||
String? approvalStatus;
|
||||
int? approvalStatusID;
|
||||
num? companyShare;
|
||||
num? companyShareWithTax;
|
||||
num? companyTaxAmount;
|
||||
num? discountAmount;
|
||||
int? discountCategory;
|
||||
String? discountType;
|
||||
num? discountTypeValue;
|
||||
bool? isApprovalCreated;
|
||||
bool? isApprovalRequired;
|
||||
dynamic isCheckInAllow;
|
||||
bool? isCovered;
|
||||
bool? isLab;
|
||||
DateTime? orderDate;
|
||||
int? orderLineItemNo;
|
||||
int? orderNo;
|
||||
int? partnerID;
|
||||
num? partnerShare;
|
||||
String? partnerShareType;
|
||||
num? patientShare;
|
||||
num? patientShareWithTax;
|
||||
num? patientTaxAmount;
|
||||
num? procPrice;
|
||||
int? procedureCategoryID;
|
||||
String? procedureCategoryName;
|
||||
String? procedureID;
|
||||
String? procedureName;
|
||||
num? taxAmount;
|
||||
num? taxPct;
|
||||
|
||||
AncillaryOrderProcDetail({
|
||||
this.approvalLineItemNo,
|
||||
this.approvalNo,
|
||||
this.approvalStatus,
|
||||
this.approvalStatusID,
|
||||
this.companyShare,
|
||||
this.companyShareWithTax,
|
||||
this.companyTaxAmount,
|
||||
this.discountAmount,
|
||||
this.discountCategory,
|
||||
this.discountType,
|
||||
this.discountTypeValue,
|
||||
this.isApprovalCreated,
|
||||
this.isApprovalRequired,
|
||||
this.isCheckInAllow,
|
||||
this.isCovered,
|
||||
this.isLab,
|
||||
this.orderDate,
|
||||
this.orderLineItemNo,
|
||||
this.orderNo,
|
||||
this.partnerID,
|
||||
this.partnerShare,
|
||||
this.partnerShareType,
|
||||
this.patientShare,
|
||||
this.patientShareWithTax,
|
||||
this.patientTaxAmount,
|
||||
this.procPrice,
|
||||
this.procedureCategoryID,
|
||||
this.procedureCategoryName,
|
||||
this.procedureID,
|
||||
this.procedureName,
|
||||
this.taxAmount,
|
||||
this.taxPct,
|
||||
});
|
||||
|
||||
factory AncillaryOrderProcDetail.fromJson(Map<String, dynamic> json) => AncillaryOrderProcDetail(
|
||||
approvalLineItemNo: json['ApprovalLineItemNo'] as int?,
|
||||
approvalNo: json['ApprovalNo'] as int?,
|
||||
approvalStatus: json['ApprovalStatus'] as String?,
|
||||
approvalStatusID: json['ApprovalStatusID'] as int?,
|
||||
companyShare: _toNum(json['CompanyShare']),
|
||||
companyShareWithTax: _toNum(json['CompanyShareWithTax']),
|
||||
companyTaxAmount: _toNum(json['CompanyTaxAmount']),
|
||||
discountAmount: _toNum(json['DiscountAmount']),
|
||||
discountCategory: json['DiscountCategory'] as int?,
|
||||
discountType: json['DiscountType'] as String?,
|
||||
discountTypeValue: _toNum(json['DiscountTypeValue']),
|
||||
isApprovalCreated: json['IsApprovalCreated'] as bool?,
|
||||
isApprovalRequired: json['IsApprovalRequired'] as bool?,
|
||||
isCheckInAllow: json['IsCheckInAllow'],
|
||||
isCovered: json['IsCovered'] as bool?,
|
||||
isLab: json['IsLab'] as bool?,
|
||||
orderDate: DateUtil.convertStringToDate(json['OrderDate']),
|
||||
orderLineItemNo: json['OrderLineItemNo'] as int?,
|
||||
orderNo: json['OrderNo'] as int?,
|
||||
partnerID: json['PartnerID'] as int?,
|
||||
partnerShare: _toNum(json['PartnerShare']),
|
||||
partnerShareType: json['PartnerShareType'] as String?,
|
||||
patientShare: _toNum(json['PatientShare']),
|
||||
patientShareWithTax: _toNum(json['PatientShareWithTax']),
|
||||
patientTaxAmount: _toNum(json['PatientTaxAmount']),
|
||||
procPrice: _toNum(json['ProcPrice']),
|
||||
procedureCategoryID: json['ProcedureCategoryID'] as int?,
|
||||
procedureCategoryName: json['ProcedureCategoryName'] as String?,
|
||||
procedureID: json['ProcedureID'] as String?,
|
||||
procedureName: json['ProcedureName'] as String?,
|
||||
taxAmount: _toNum(json['TaxAmount']),
|
||||
taxPct: _toNum(json['TaxPct']),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to safely parse numeric fields that may be int/double/string/null
|
||||
num? _toNum(dynamic v) {
|
||||
if (v == null) return null;
|
||||
if (v is num) return v;
|
||||
if (v is String) return num.tryParse(v);
|
||||
return null;
|
||||
}
|
||||
@ -0,0 +1,379 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:hmg_patient_app_new/core/api/api_client.dart';
|
||||
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
|
||||
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/services/logger_service.dart';
|
||||
|
||||
abstract class TodoSectionRepo {
|
||||
Future<Either<Failure, GenericApiModel<List<AncillaryOrderItem>>>> getOnlineAncillaryOrderList();
|
||||
|
||||
Future<Either<Failure, GenericApiModel<List<AncillaryOrderProcedureItem>>>> getOnlineAncillaryOrderDetailsProceduresList({
|
||||
required int appointmentNoVida,
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
});
|
||||
|
||||
Future<Either<Failure, dynamic>> checkPaymentStatus({required String transID});
|
||||
|
||||
Future<Either<Failure, dynamic>> createAdvancePayment({
|
||||
required int projectID,
|
||||
required double paymentAmount,
|
||||
required String paymentReference,
|
||||
required String paymentMethodName,
|
||||
required int patientTypeID,
|
||||
required String patientName,
|
||||
required int patientID,
|
||||
required String setupID,
|
||||
required bool isAncillaryOrder,
|
||||
});
|
||||
|
||||
Future<Either<Failure, dynamic>> addAdvancedNumberRequest({
|
||||
required String advanceNumber,
|
||||
required String paymentReference,
|
||||
required int appointmentID,
|
||||
required int patientID,
|
||||
required int patientTypeID,
|
||||
required int patientOutSA,
|
||||
});
|
||||
|
||||
Future<Either<Failure, dynamic>> autoGenerateAncillaryOrdersInvoice({
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
required int appointmentNo,
|
||||
required List<dynamic> selectedProcedures,
|
||||
required int languageID,
|
||||
});
|
||||
|
||||
Future<Either<Failure, dynamic>> applePayInsertRequest({required dynamic applePayInsertRequest});
|
||||
}
|
||||
|
||||
class TodoSectionRepoImp implements TodoSectionRepo {
|
||||
final ApiClient apiClient;
|
||||
final LoggerService loggerService;
|
||||
|
||||
TodoSectionRepoImp({required this.loggerService, required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel<List<AncillaryOrderItem>>>> getOnlineAncillaryOrderList() async {
|
||||
Map<String, dynamic> mapDevice = {};
|
||||
|
||||
try {
|
||||
GenericApiModel<List<AncillaryOrderItem>>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.getOnlineAncillaryOrderList,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
List<AncillaryOrderItem> ancillaryOrders = [];
|
||||
|
||||
// Parse the nested structure
|
||||
if (response['AncillaryOrderList'] != null && response['AncillaryOrderList'] is List) {
|
||||
final groupsList = response['AncillaryOrderList'] as List;
|
||||
|
||||
// Iterate through each group
|
||||
for (var group in groupsList) {
|
||||
if (group is Map<String, dynamic> && group['AncillaryOrderList'] != null) {
|
||||
final ordersList = group['AncillaryOrderList'] as List;
|
||||
final projectName = group['ProjectName'] as String?;
|
||||
final projectID = group['ProjectID'] as int?;
|
||||
|
||||
// Parse each order item in the group
|
||||
for (var orderJson in ordersList) {
|
||||
if (orderJson is Map<String, dynamic>) {
|
||||
ancillaryOrders.add(AncillaryOrderItem.fromJson(orderJson, projectName: projectName, projectID: projectID));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apiResponse = GenericApiModel<List<AncillaryOrderItem>>(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: errorMessage,
|
||||
data: ancillaryOrders,
|
||||
);
|
||||
} catch (e) {
|
||||
loggerService.logInfo("Error parsing ancillary orders: ${e.toString()}");
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in getOnlineAncillaryOrderList: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel<List<AncillaryOrderProcedureItem>>>> getOnlineAncillaryOrderDetailsProceduresList({
|
||||
required int appointmentNoVida,
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
'AppointmentNo_Vida': appointmentNoVida,
|
||||
'OrderNo': orderNo,
|
||||
'ProjectID': projectID,
|
||||
};
|
||||
|
||||
try {
|
||||
GenericApiModel<List<AncillaryOrderProcedureItem>>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.getOnlineAncillaryOrderProcList,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
List<AncillaryOrderProcedureItem> ancillaryOrdersProcedures = [];
|
||||
|
||||
// Parse the flat array structure (NOT nested like AncillaryOrderList)
|
||||
if (response['AncillaryOrderProcList'] != null && response['AncillaryOrderProcList'] is List) {
|
||||
final procList = response['AncillaryOrderProcList'] as List;
|
||||
|
||||
// Parse each procedure item directly
|
||||
for (var procJson in procList) {
|
||||
if (procJson is Map<String, dynamic>) {
|
||||
ancillaryOrdersProcedures.add(AncillaryOrderProcedureItem.fromJson(procJson));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apiResponse = GenericApiModel<List<AncillaryOrderProcedureItem>>(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: errorMessage,
|
||||
data: ancillaryOrdersProcedures,
|
||||
);
|
||||
} catch (e) {
|
||||
loggerService.logError("Error parsing ancillary Procedures: ${e.toString()}");
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in getOnlineAncillaryOrderDetailsProceduresList: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, dynamic>> checkPaymentStatus({required String transID}) async {
|
||||
Map<String, dynamic> mapDevice = {'ClientRequestID': transID};
|
||||
|
||||
try {
|
||||
dynamic apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.getRequestStatusByRequestID,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
apiResponse = response;
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
return Right(apiResponse);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in checkPaymentStatus: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, dynamic>> createAdvancePayment({
|
||||
required int projectID,
|
||||
required double paymentAmount,
|
||||
required String paymentReference,
|
||||
required String paymentMethodName,
|
||||
required int patientTypeID,
|
||||
required String patientName,
|
||||
required int patientID,
|
||||
required String setupID,
|
||||
required bool isAncillaryOrder,
|
||||
}) async {
|
||||
// //VersionID (number)
|
||||
// // Channel (number)
|
||||
// // IPAdress (string)
|
||||
// // generalid (string)
|
||||
// // LanguageID (number)
|
||||
// // Latitude (number)
|
||||
// // Longitude (number)
|
||||
// // DeviceTypeID (number)
|
||||
// // PatientType (number)
|
||||
// // PatientTypeID (number)
|
||||
// // PatientID (number)
|
||||
// // PatientOutSA (number)
|
||||
// // TokenID (string)
|
||||
// // SessionID (string)
|
||||
|
||||
Map<String, dynamic> mapDevice = {
|
||||
'CustName': patientName,
|
||||
'CustID': patientID,
|
||||
'SetupID': setupID,
|
||||
'ProjectID': projectID,
|
||||
'AccountID': patientID,
|
||||
'PaymentAmount': paymentAmount,
|
||||
'NationalityID': null,
|
||||
'DepositorName': patientName,
|
||||
'CreatedBy': 3,
|
||||
'PaymentMethodName': paymentMethodName,
|
||||
'PaymentReference': paymentReference,
|
||||
'PaymentMethod': paymentMethodName,
|
||||
'IsAncillaryOrder': isAncillaryOrder,
|
||||
};
|
||||
|
||||
try {
|
||||
dynamic apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.createAdvancePayments,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
apiResponse = response;
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
return Right(apiResponse);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in createAdvancePayment: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, dynamic>> addAdvancedNumberRequest({
|
||||
required String advanceNumber,
|
||||
required String paymentReference,
|
||||
required int appointmentID,
|
||||
required int patientID,
|
||||
required int patientTypeID,
|
||||
required int patientOutSA,
|
||||
}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
'AdvanceNumber': advanceNumber,
|
||||
'PaymentReference': paymentReference,
|
||||
'AppointmentID': appointmentID,
|
||||
'PatientID': patientID,
|
||||
'PatientTypeID': patientTypeID,
|
||||
'PatientOutSA': patientOutSA,
|
||||
};
|
||||
|
||||
try {
|
||||
dynamic apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.addAdvanceNumberRequest,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
apiResponse = response;
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
return Right(apiResponse);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in addAdvancedNumberRequest: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, dynamic>> autoGenerateAncillaryOrdersInvoice({
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
required int appointmentNo,
|
||||
required List<dynamic> selectedProcedures,
|
||||
required int languageID,
|
||||
}) async {
|
||||
// Extract procedure IDs from selectedProcedures
|
||||
List<String> procedureOrderIDs = [];
|
||||
selectedProcedures.forEach((element) {
|
||||
procedureOrderIDs.add(element["ProcedureID"].toString());
|
||||
});
|
||||
|
||||
Map<String, dynamic> mapDevice = {
|
||||
'LanguageID': languageID,
|
||||
'RequestAncillaryOrderInvoice': [
|
||||
{
|
||||
'MemberID': 102,
|
||||
'ProjectID': projectID,
|
||||
'AppointmentNo': appointmentNo,
|
||||
'OrderNo': orderNo,
|
||||
'AncillaryOrderInvoiceProcList': selectedProcedures,
|
||||
}
|
||||
],
|
||||
'ProcedureOrderIds': procedureOrderIDs,
|
||||
};
|
||||
|
||||
try {
|
||||
dynamic apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.autoGenerateAncillaryOrdersInvoice,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
apiResponse = response;
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
return Right(apiResponse);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in autoGenerateAncillaryOrdersInvoice: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, dynamic>> applePayInsertRequest({required dynamic applePayInsertRequest}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
'ApplePayInsertRequest': applePayInsertRequest,
|
||||
};
|
||||
|
||||
try {
|
||||
dynamic apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
ApiConsts.applePayInsertRequest,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
apiResponse = response;
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
return Right(apiResponse);
|
||||
} catch (e) {
|
||||
loggerService.logError("Unknown error in applePayInsertRequest: ${e.toString()}");
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
|
||||
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
|
||||
|
||||
class TodoSectionViewModel extends ChangeNotifier {
|
||||
TodoSectionRepo todoSectionRepo;
|
||||
ErrorHandlerService errorHandlerService;
|
||||
|
||||
TodoSectionViewModel({required this.todoSectionRepo, required this.errorHandlerService});
|
||||
|
||||
initializeTodoSectionViewModel() async {
|
||||
patientAncillaryOrdersList.clear();
|
||||
isAncillaryOrdersLoading = true;
|
||||
isAncillaryDetailsProceduresLoading = true;
|
||||
await getPatientOnlineAncillaryOrderList();
|
||||
}
|
||||
|
||||
bool isAncillaryOrdersLoading = false;
|
||||
bool isAncillaryDetailsProceduresLoading = false;
|
||||
bool isProcessingPayment = false;
|
||||
List<AncillaryOrderItem> patientAncillaryOrdersList = [];
|
||||
List<AncillaryOrderProcedureItem> patientAncillaryOrderProceduresList = [];
|
||||
|
||||
void setProcessingPayment(bool value) {
|
||||
isProcessingPayment = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
patientAncillaryOrdersList.clear();
|
||||
isAncillaryOrdersLoading = true;
|
||||
notifyListeners();
|
||||
final result = await todoSectionRepo.getOnlineAncillaryOrderList();
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
isAncillaryOrdersLoading = false;
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
},
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
patientAncillaryOrdersList = apiResponse.data!;
|
||||
isAncillaryOrdersLoading = false;
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> getPatientOnlineAncillaryOrderDetailsProceduresList({
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
required int appointmentNoVida,
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
}) async {
|
||||
isAncillaryDetailsProceduresLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
final result = await todoSectionRepo.getOnlineAncillaryOrderDetailsProceduresList(
|
||||
appointmentNoVida: appointmentNoVida,
|
||||
orderNo: orderNo,
|
||||
projectID: projectID,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
isAncillaryDetailsProceduresLoading = false;
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
},
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
patientAncillaryOrderProceduresList = apiResponse.data!;
|
||||
isAncillaryDetailsProceduresLoading = false;
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> checkPaymentStatus({
|
||||
required String transID,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
final result = await todoSectionRepo.checkPaymentStatus(transID: transID);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> createAdvancePayment({
|
||||
required int projectID,
|
||||
required double paymentAmount,
|
||||
required String paymentReference,
|
||||
required String paymentMethodName,
|
||||
required int patientTypeID,
|
||||
required String patientName,
|
||||
required int patientID,
|
||||
required String setupID,
|
||||
required bool isAncillaryOrder,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
final result = await todoSectionRepo.createAdvancePayment(
|
||||
projectID: projectID,
|
||||
paymentAmount: paymentAmount,
|
||||
paymentReference: paymentReference,
|
||||
paymentMethodName: paymentMethodName,
|
||||
patientTypeID: patientTypeID,
|
||||
patientName: patientName,
|
||||
patientID: patientID,
|
||||
setupID: setupID,
|
||||
isAncillaryOrder: isAncillaryOrder,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addAdvancedNumberRequest({
|
||||
required String advanceNumber,
|
||||
required String paymentReference,
|
||||
required int appointmentID,
|
||||
required int patientID,
|
||||
required int patientTypeID,
|
||||
required int patientOutSA,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
final result = await todoSectionRepo.addAdvancedNumberRequest(
|
||||
advanceNumber: advanceNumber,
|
||||
paymentReference: paymentReference,
|
||||
appointmentID: appointmentID,
|
||||
patientID: patientID,
|
||||
patientTypeID: patientTypeID,
|
||||
patientOutSA: patientOutSA,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> autoGenerateAncillaryOrdersInvoice({
|
||||
required int orderNo,
|
||||
required int projectID,
|
||||
required int appointmentNo,
|
||||
required List<dynamic> selectedProcedures,
|
||||
required int languageID,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
final result = await todoSectionRepo.autoGenerateAncillaryOrdersInvoice(
|
||||
orderNo: orderNo,
|
||||
projectID: projectID,
|
||||
appointmentNo: appointmentNo,
|
||||
selectedProcedures: selectedProcedures,
|
||||
languageID: languageID,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> applePayInsertRequest({
|
||||
required dynamic applePayInsertRequest,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
final result = await todoSectionRepo.applePayInsertRequest(applePayInsertRequest: applePayInsertRequest);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
|
||||
class ToDoPage extends StatefulWidget {
|
||||
const ToDoPage({super.key});
|
||||
|
||||
@override
|
||||
State<ToDoPage> createState() => _ToDoPageState();
|
||||
}
|
||||
|
||||
class _ToDoPageState extends State<ToDoPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CollapsingListView(
|
||||
title: "ToDo List".needTranslation,
|
||||
isLeading: false,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 16.h),
|
||||
"Ancillary Orders".needTranslation.toText18(isBold: true),
|
||||
|
||||
],
|
||||
).paddingSymmetrical(24.w, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,644 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_state.dart';
|
||||
import 'package:hmg_patient_app_new/core/cache_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||
import 'package:hmg_patient_app_new/core/enums.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AncillaryOrderPaymentPage extends StatefulWidget {
|
||||
final DateTime? appointmentDate;
|
||||
final int appointmentNoVida;
|
||||
final int orderNo;
|
||||
final int projectID;
|
||||
final List<AncillaryOrderProcDetail> selectedProcedures;
|
||||
final double totalAmount;
|
||||
|
||||
const AncillaryOrderPaymentPage({
|
||||
super.key,
|
||||
required this.appointmentDate,
|
||||
required this.appointmentNoVida,
|
||||
required this.orderNo,
|
||||
required this.projectID,
|
||||
required this.selectedProcedures,
|
||||
required this.totalAmount,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AncillaryOrderPaymentPage> createState() => _AncillaryOrderPaymentPageState();
|
||||
}
|
||||
|
||||
class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
|
||||
late PayfortViewModel payfortViewModel;
|
||||
late AppState appState;
|
||||
late TodoSectionViewModel todoSectionViewModel;
|
||||
|
||||
MyInAppBrowser? browser;
|
||||
String selectedPaymentMethod = "";
|
||||
String transID = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scheduleMicrotask(() {
|
||||
payfortViewModel.initPayfortViewModel();
|
||||
payfortViewModel.setIsApplePayConfigurationLoading(false);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
appState = getIt.get<AppState>();
|
||||
todoSectionViewModel = Provider.of<TodoSectionViewModel>(context);
|
||||
payfortViewModel = Provider.of<PayfortViewModel>(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.bgScaffoldColor,
|
||||
body: Consumer<TodoSectionViewModel>(
|
||||
builder: (context, todoVM, child) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CollapsingListView(
|
||||
title: "Select Payment Method".needTranslation,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// Mada Payment Option
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
SizedBox(height: 16.h),
|
||||
"Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 8.h),
|
||||
const Spacer(),
|
||||
Transform.flip(
|
||||
flipX: appState.isArabic(),
|
||||
child: Utils.buildSvgWithAssets(
|
||||
icon: AppAssets.forward_arrow_icon_small,
|
||||
iconColor: AppColors.blackColor,
|
||||
width: 18.h,
|
||||
height: 13.h,
|
||||
fit: BoxFit.contain,
|
||||
).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(16.h, 16.h),
|
||||
).paddingSymmetrical(24.h, 0.h).onPress(() {
|
||||
if (!todoVM.isProcessingPayment) {
|
||||
selectedPaymentMethod = "MADA";
|
||||
_openPaymentURL("mada");
|
||||
}
|
||||
}),
|
||||
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
// Visa/Mastercard Payment Option
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(AppAssets.visa, width: 50.h, height: 50.h),
|
||||
SizedBox(width: 8.h),
|
||||
Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h),
|
||||
],
|
||||
).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
SizedBox(height: 16.h),
|
||||
"Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 8.h),
|
||||
const Spacer(),
|
||||
Transform.flip(
|
||||
flipX: appState.isArabic(),
|
||||
child: Utils.buildSvgWithAssets(
|
||||
icon: AppAssets.forward_arrow_icon_small,
|
||||
iconColor: AppColors.blackColor,
|
||||
width: 18.h,
|
||||
height: 13.h,
|
||||
fit: BoxFit.contain,
|
||||
).toShimmer2(isShow: todoVM.isProcessingPayment),
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(16.h, 16.h),
|
||||
).paddingSymmetrical(24.h, 0.h).onPress(() {
|
||||
if (!todoVM.isProcessingPayment) {
|
||||
selectedPaymentMethod = "VISA";
|
||||
_openPaymentURL("visa");
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Payment Summary Footer
|
||||
todoVM.isProcessingPayment ? SizedBox.shrink() : _buildPaymentSummary()
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentSummary() {
|
||||
// Calculate amounts
|
||||
double amountBeforeTax = 0.0;
|
||||
double taxAmount = 0.0;
|
||||
|
||||
for (var proc in widget.selectedProcedures) {
|
||||
amountBeforeTax += (proc.patientShare ?? 0);
|
||||
taxAmount += (proc.patientTaxAmount ?? 0);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 24.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Consumer<PayfortViewModel>(builder: (context, payfortVM, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24.h),
|
||||
"Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
|
||||
SizedBox(height: 17.h),
|
||||
|
||||
// Amount before tax
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"Amount before tax".needTranslation.toText14(isBold: true),
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
amountBeforeTax.toString().toText16(isBold: true),
|
||||
AppColors.blackColor,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
|
||||
// VAT 15%
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
taxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor),
|
||||
AppColors.greyTextColor,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
|
||||
SizedBox(height: 17.h),
|
||||
|
||||
// Total Amount
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"".needTranslation.toText14(isBold: true),
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
widget.totalAmount.toString().toText24(isBold: true),
|
||||
AppColors.blackColor,
|
||||
17,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
|
||||
// Apple Pay Button (iOS only)
|
||||
Platform.isIOS && Utils.havePrivilege(103)
|
||||
? Utils.buildSvgWithAssets(
|
||||
icon: AppAssets.apple_pay_button,
|
||||
width: 200.h,
|
||||
height: 80.h,
|
||||
fit: BoxFit.contain,
|
||||
).paddingSymmetrical(24.h, 0.h).onPress(() {
|
||||
if (!todoSectionViewModel.isProcessingPayment) {
|
||||
_startApplePay();
|
||||
}
|
||||
})
|
||||
: SizedBox(height: 12.h),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _openPaymentURL(String paymentMethod) {
|
||||
todoSectionViewModel.setProcessingPayment(true);
|
||||
|
||||
browser = MyInAppBrowser(
|
||||
onExitCallback: _onBrowserExit,
|
||||
onLoadStartCallback: _onBrowserLoadStart,
|
||||
);
|
||||
|
||||
final user = appState.getAuthenticatedUser();
|
||||
transID = Utils.getAdvancePaymentTransID(
|
||||
widget.projectID,
|
||||
user!.patientId!,
|
||||
);
|
||||
|
||||
browser!.openPaymentBrowser(
|
||||
widget.totalAmount,
|
||||
"Ancillary Order Payment",
|
||||
transID,
|
||||
widget.projectID.toString(),
|
||||
user.emailAddress ?? "CustID_${user.patientId}@HMG.com",
|
||||
paymentMethod,
|
||||
user.patientType ?? 1,
|
||||
"${user.firstName} ${user.lastName}",
|
||||
user.patientId,
|
||||
user,
|
||||
browser!,
|
||||
false,
|
||||
"3",
|
||||
ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum().toString(),
|
||||
context,
|
||||
null,
|
||||
widget.appointmentNoVida,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
void _onBrowserLoadStart(String url) {
|
||||
log("onBrowserLoadStart: $url");
|
||||
|
||||
for (var element in MyInAppBrowser.successURLS) {
|
||||
if (url.contains(element)) {
|
||||
if (browser!.isOpened()) browser!.close();
|
||||
MyInAppBrowser.isPaymentDone = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (var element in MyInAppBrowser.errorURLS) {
|
||||
if (url.contains(element)) {
|
||||
if (browser!.isOpened()) browser!.close();
|
||||
MyInAppBrowser.isPaymentDone = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onBrowserExit(bool isPaymentMade) {
|
||||
log("onBrowserExit Called: $isPaymentMade");
|
||||
_checkPaymentStatus();
|
||||
}
|
||||
|
||||
void _checkPaymentStatus() {
|
||||
LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation);
|
||||
|
||||
todoSectionViewModel.checkPaymentStatus(
|
||||
transID: transID,
|
||||
onSuccess: (response) {
|
||||
String paymentInfo = response['Response_Message'];
|
||||
|
||||
if (paymentInfo == 'Success') {
|
||||
// Extract payment details from response
|
||||
final paymentAmount = response['Amount'] ?? widget.totalAmount;
|
||||
final fortId = response['Fort_id'] ?? transID;
|
||||
final paymentMethod = response['PaymentMethod'] ?? selectedPaymentMethod;
|
||||
|
||||
// Call createAdvancePayment with the payment details
|
||||
_createAdvancePayment(
|
||||
paymentAmount: paymentAmount is String ? double.parse(paymentAmount) : paymentAmount.toDouble(),
|
||||
paymentReference: fortId,
|
||||
paymentMethod: paymentMethod,
|
||||
);
|
||||
} else {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast(response['Response_Message']);
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _createAdvancePayment({
|
||||
required double paymentAmount,
|
||||
required String paymentReference,
|
||||
required String paymentMethod,
|
||||
}) {
|
||||
LoaderBottomSheet.showLoader(loadingText: "Processing payment, Please wait...".needTranslation);
|
||||
|
||||
final user = appState.getAuthenticatedUser();
|
||||
|
||||
todoSectionViewModel.createAdvancePayment(
|
||||
projectID: widget.projectID,
|
||||
paymentAmount: paymentAmount,
|
||||
paymentReference: paymentReference,
|
||||
paymentMethodName: paymentMethod,
|
||||
patientTypeID: user!.patientType ?? 1,
|
||||
patientName: "${user.firstName} ${user.lastName}",
|
||||
patientID: user.patientId!,
|
||||
setupID: "010266",
|
||||
isAncillaryOrder: true,
|
||||
onSuccess: (response) {
|
||||
// Extract advance number from response
|
||||
final advanceNumber =
|
||||
response['OnlineCheckInAppointments']?[0]?['AdvanceNumber'] ?? response['OnlineCheckInAppointments']?[0]?['AdvanceNumber_VP'] ?? '';
|
||||
|
||||
if (advanceNumber.isNotEmpty) {
|
||||
_addAdvancedNumberRequest(
|
||||
advanceNumber: advanceNumber.toString(),
|
||||
paymentReference: paymentReference,
|
||||
);
|
||||
} else {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast("Failed to get advance number");
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _addAdvancedNumberRequest({
|
||||
required String advanceNumber,
|
||||
required String paymentReference,
|
||||
}) {
|
||||
LoaderBottomSheet.showLoader(loadingText: "Finalizing payment, Please wait...".needTranslation);
|
||||
|
||||
final user = appState.getAuthenticatedUser();
|
||||
|
||||
todoSectionViewModel.addAdvancedNumberRequest(
|
||||
advanceNumber: advanceNumber,
|
||||
paymentReference: paymentReference,
|
||||
appointmentID: 0,
|
||||
patientID: user!.patientId!,
|
||||
patientTypeID: user.patientType ?? 1,
|
||||
patientOutSA: user.outSa ?? 0,
|
||||
onSuccess: (response) {
|
||||
// After adding advance number, generate invoice
|
||||
_autoGenerateInvoice();
|
||||
},
|
||||
onError: (error) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _autoGenerateInvoice() {
|
||||
LoaderBottomSheet.showLoader(loadingText: "Generating invoice, Please wait...".needTranslation);
|
||||
|
||||
List<dynamic> selectedProcListAPI = widget.selectedProcedures.map((element) {
|
||||
return {
|
||||
"ApprovalLineItemNo": element.approvalLineItemNo,
|
||||
"OrderLineItemNo": element.orderLineItemNo,
|
||||
"ProcedureID": element.procedureID,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
todoSectionViewModel.autoGenerateAncillaryOrdersInvoice(
|
||||
orderNo: widget.orderNo,
|
||||
projectID: widget.projectID,
|
||||
appointmentNo: widget.appointmentNoVida,
|
||||
selectedProcedures: selectedProcListAPI,
|
||||
languageID: appState.isArabic() ? 1 : 2,
|
||||
onSuccess: (response) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
|
||||
final invoiceNo = response['AncillaryOrderInvoiceList']?[0]?['InvoiceNo'];
|
||||
|
||||
_showSuccessDialog(invoiceNo);
|
||||
},
|
||||
onError: (error) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
Utils.showToast(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showSuccessDialog(dynamic invoiceNo) {
|
||||
todoSectionViewModel.setProcessingPayment(false);
|
||||
|
||||
log("Ancillary order payment successful! Invoice #: $invoiceNo");
|
||||
|
||||
// Show success message and navigate
|
||||
Utils.showToast("Payment successful! Invoice #: $invoiceNo");
|
||||
// Navigate back to home after a short delay
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
"Here is your invoice #: ".needTranslation.toText14(
|
||||
color: AppColors.textColorLight,
|
||||
weight: FontWeight.w500,
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
("12345").toText16(isBold: true),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
height: 56.h,
|
||||
text: LocaleKeys.ok.tr(),
|
||||
onPressed: () {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
CustomPageRoute(
|
||||
page: LandingNavigation(),
|
||||
),
|
||||
(r) => false);
|
||||
},
|
||||
backgroundColor: AppColors.secondaryLightRedColor,
|
||||
borderColor: AppColors.secondaryLightRedColor,
|
||||
textColor: AppColors.primaryRedColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// title: "Payment Completed Successfully".needTranslation,
|
||||
titleWidget: Utils.getSuccessWidget(loadingText: "Payment Completed Successfully".needTranslation),
|
||||
isCloseButtonVisible: false,
|
||||
isDismissible: false,
|
||||
isFullScreen: false,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_startApplePay() async {
|
||||
showCommonBottomSheet(
|
||||
context,
|
||||
child: Utils.getLoadingWidget(),
|
||||
callBackFunc: (str) {},
|
||||
title: "",
|
||||
height: ResponsiveExtension.screenHeight * 0.3,
|
||||
isCloseButtonVisible: false,
|
||||
isDismissible: false,
|
||||
isFullScreen: false,
|
||||
);
|
||||
final user = appState.getAuthenticatedUser();
|
||||
transID = Utils.getAdvancePaymentTransID(widget.projectID, user!.patientId!);
|
||||
|
||||
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
|
||||
await payfortViewModel.getPayfortConfigurations(
|
||||
serviceId: ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum(),
|
||||
projectId: widget.projectID,
|
||||
integrationId: 2,
|
||||
);
|
||||
|
||||
applePayInsertRequest.clientRequestID = transID;
|
||||
applePayInsertRequest.clinicID = 0;
|
||||
|
||||
applePayInsertRequest.currency = appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
|
||||
applePayInsertRequest.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com";
|
||||
applePayInsertRequest.customerID = appState.getAuthenticatedUser()!.patientId.toString();
|
||||
applePayInsertRequest.customerName = "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}";
|
||||
|
||||
applePayInsertRequest.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
|
||||
applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken);
|
||||
applePayInsertRequest.doctorID = 0;
|
||||
applePayInsertRequest.projectID = widget.projectID.toString();
|
||||
applePayInsertRequest.serviceID = ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum().toString();
|
||||
applePayInsertRequest.channelID = 3;
|
||||
applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString();
|
||||
applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType;
|
||||
applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa;
|
||||
applePayInsertRequest.appointmentDate = DateUtil.convertDateToString(widget.appointmentDate ?? DateTime.now());
|
||||
applePayInsertRequest.appointmentNo = widget.appointmentNoVida;
|
||||
applePayInsertRequest.orderDescription = "Ancillary Order Payment";
|
||||
applePayInsertRequest.liveServiceID = "0";
|
||||
applePayInsertRequest.latitude = "0.0";
|
||||
applePayInsertRequest.longitude = "0.0";
|
||||
applePayInsertRequest.amount = widget.totalAmount.toString();
|
||||
applePayInsertRequest.isSchedule = "0";
|
||||
applePayInsertRequest.language = appState.isArabic() ? 'ar' : 'en';
|
||||
applePayInsertRequest.languageID = appState.isArabic() ? 1 : 2;
|
||||
applePayInsertRequest.userName = appState.getAuthenticatedUser()!.patientId;
|
||||
applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html";
|
||||
applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html";
|
||||
applePayInsertRequest.paymentOption = "ApplePay";
|
||||
|
||||
applePayInsertRequest.isMobSDK = true;
|
||||
applePayInsertRequest.merchantReference = transID;
|
||||
applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier;
|
||||
applePayInsertRequest.commandType = "PURCHASE";
|
||||
applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature;
|
||||
applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode;
|
||||
applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest;
|
||||
applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse;
|
||||
applePayInsertRequest.returnURL = "";
|
||||
|
||||
try {
|
||||
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest);
|
||||
} catch (error) {
|
||||
log("Apple Pay Insert Request Failed: $error");
|
||||
Navigator.of(context).pop();
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Utils.getErrorWidget(loadingText: "Failed to initialize Apple Pay. Please try again.".needTranslation),
|
||||
callBackFunc: () {},
|
||||
isFullScreen: false,
|
||||
isCloseButtonVisible: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Only proceed with Apple Pay if insert was successful
|
||||
payfortViewModel.paymentWithApplePay(
|
||||
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
|
||||
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
|
||||
orderDescription: "Ancillary Order Payment",
|
||||
orderAmount: widget.totalAmount,
|
||||
merchantReference: transID,
|
||||
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
|
||||
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
|
||||
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
|
||||
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
|
||||
onFailed: (failureResult) async {
|
||||
log("failureResult: ${failureResult.message.toString()}");
|
||||
Navigator.of(context).pop();
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
|
||||
callBackFunc: () {},
|
||||
isFullScreen: false,
|
||||
isCloseButtonVisible: true,
|
||||
);
|
||||
},
|
||||
onSucceeded: (successResult) async {
|
||||
Navigator.of(context).pop();
|
||||
log("successResult: ${successResult.responseMessage.toString()}");
|
||||
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
|
||||
_checkPaymentStatus();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,656 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_state.dart';
|
||||
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_order_payment_page.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AncillaryOrderDetailsList extends StatefulWidget {
|
||||
final int appointmentNoVida;
|
||||
final int orderNo;
|
||||
final int projectID;
|
||||
final String projectName;
|
||||
|
||||
const AncillaryOrderDetailsList({
|
||||
super.key,
|
||||
required this.appointmentNoVida,
|
||||
required this.orderNo,
|
||||
required this.projectID,
|
||||
required this.projectName,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AncillaryOrderDetailsList> createState() => _AncillaryOrderDetailsListState();
|
||||
}
|
||||
|
||||
class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
|
||||
late TodoSectionViewModel todoSectionViewModel;
|
||||
late AppState appState;
|
||||
List<AncillaryOrderProcDetail> selectedProcedures = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
appState = getIt.get<AppState>();
|
||||
todoSectionViewModel = context.read<TodoSectionViewModel>();
|
||||
scheduleMicrotask(() async {
|
||||
await todoSectionViewModel.getPatientOnlineAncillaryOrderDetailsProceduresList(
|
||||
appointmentNoVida: widget.appointmentNoVida,
|
||||
orderNo: widget.orderNo,
|
||||
projectID: widget.projectID,
|
||||
onSuccess: (response) {
|
||||
_autoSelectEligibleProcedures();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _autoSelectEligibleProcedures() {
|
||||
selectedProcedures.clear();
|
||||
if (todoSectionViewModel.patientAncillaryOrderProceduresList.isNotEmpty) {
|
||||
final procedures = todoSectionViewModel.patientAncillaryOrderProceduresList[0].ancillaryOrderProcDetailsList;
|
||||
if (procedures != null) {
|
||||
for (var proc in procedures) {
|
||||
if (!_isProcedureDisabled(proc)) {
|
||||
selectedProcedures.add(proc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
bool _isProcedureDisabled(AncillaryOrderProcDetail procedure) {
|
||||
// return true;
|
||||
return (procedure.isApprovalRequired == true && procedure.isApprovalCreated == false) ||
|
||||
(procedure.isApprovalCreated == true && procedure.approvalNo == 0) ||
|
||||
(procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0);
|
||||
}
|
||||
|
||||
bool _isProcedureSelected(AncillaryOrderProcDetail procedure) {
|
||||
return selectedProcedures.contains(procedure);
|
||||
}
|
||||
|
||||
void _toggleProcedureSelection(AncillaryOrderProcDetail procedure) {
|
||||
setState(() {
|
||||
if (_isProcedureSelected(procedure)) {
|
||||
selectedProcedures.remove(procedure);
|
||||
} else {
|
||||
selectedProcedures.add(procedure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String _getApprovalStatusText(AncillaryOrderProcDetail procedure) {
|
||||
if (procedure.isApprovalRequired == false) {
|
||||
return "Cash";
|
||||
} else {
|
||||
if (procedure.isApprovalCreated == true && procedure.approvalNo != 0) {
|
||||
return "Approved";
|
||||
} else if (procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0) {
|
||||
return "Approval Rejected - Please visit receptionist";
|
||||
} else {
|
||||
return "Sent For Approval";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double _getTotalAmount() {
|
||||
double total = 0.0;
|
||||
for (var proc in selectedProcedures) {
|
||||
total += (proc.patientShareWithTax ?? 0);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.bgScaffoldColor,
|
||||
body: Consumer<TodoSectionViewModel>(builder: (context, viewModel, child) {
|
||||
AncillaryOrderProcedureItem? orderData;
|
||||
if (viewModel.patientAncillaryOrderProceduresList.isNotEmpty) {
|
||||
orderData = viewModel.patientAncillaryOrderProceduresList[0];
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CollapsingListView(
|
||||
title: "Ancillary Order Details".needTranslation,
|
||||
child: viewModel.isAncillaryDetailsProceduresLoading
|
||||
? _buildLoadingShimmer().paddingSymmetrical(24.w, 0)
|
||||
: viewModel.patientAncillaryOrderProceduresList.isEmpty
|
||||
? _buildDefaultEmptyState(context).paddingSymmetrical(24.w, 0)
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 16.h),
|
||||
if (orderData != null) _buildPatientInfoCard(orderData),
|
||||
SizedBox(height: 16.h),
|
||||
if (orderData != null) _buildProceduresSection(orderData),
|
||||
],
|
||||
).paddingSymmetrical(24.w, 0),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (orderData != null) _buildStickyPaymentButton(orderData),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingShimmer() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) {
|
||||
return AncillaryOrderCard(
|
||||
order: AncillaryOrderItem(),
|
||||
isLoading: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultEmptyState(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40.h),
|
||||
child: Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 12.r,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Utils.getNoDataWidget(
|
||||
context,
|
||||
noDataText: "No Procedures available for the selected order.".needTranslation,
|
||||
isSmallWidget: true,
|
||||
width: 62.w,
|
||||
height: 62.h,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPatientInfoCard(AncillaryOrderProcedureItem orderData) {
|
||||
final user = appState.getAuthenticatedUser();
|
||||
final patientName = orderData.patientName ?? user?.firstName ?? "N/A";
|
||||
final patientMRN = orderData.patientID ?? user?.patientId;
|
||||
final nationalID = user?.patientIdentificationNo ?? "";
|
||||
|
||||
// Determine gender for profile image (assuming 1 = male, 2 = female)
|
||||
final gender = user?.gender ?? 1;
|
||||
|
||||
return Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 24.r,
|
||||
hasShadow: true,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header Row with Profile Image, Name, and QR Code
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
gender == 1 ? AppAssets.male_img : AppAssets.femaleImg,
|
||||
width: 56.w,
|
||||
height: 56.h,
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
patientName.toText18(
|
||||
isBold: true,
|
||||
weight: FontWeight.w600,
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
maxlines: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
spacing: 4.w,
|
||||
runSpacing: 4.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
// icon: AppAssets.file_icon,
|
||||
labelText: "MRN: ${patientMRN ?? 'N/A'}",
|
||||
iconSize: 12.w,
|
||||
),
|
||||
|
||||
// National ID
|
||||
if (nationalID.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
// icon: AppAssets.card_user,
|
||||
labelText: "ID: $nationalID",
|
||||
iconSize: 12.w,
|
||||
),
|
||||
|
||||
// Appointment Number
|
||||
if (orderData.appointmentNo != null)
|
||||
AppCustomChipWidget(
|
||||
// icon: AppAssets.calendar,
|
||||
labelText: "Appt #: ${orderData.appointmentNo}",
|
||||
iconSize: 12.w,
|
||||
),
|
||||
|
||||
// Order Number
|
||||
if (orderData.ancillaryOrderProcDetailsList?.firstOrNull?.orderNo != null)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Order #: ${orderData.ancillaryOrderProcDetailsList!.first.orderNo}",
|
||||
),
|
||||
|
||||
// Blood Group
|
||||
if (user?.bloodGroup != null && user!.bloodGroup!.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
// icon: AppAssets.blood_icon,
|
||||
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w),
|
||||
labelText: "Blood: ${user.bloodGroup}",
|
||||
iconColor: AppColors.primaryRedColor,
|
||||
),
|
||||
|
||||
// Insurance Company (if applicable)
|
||||
if (orderData.companyName != null && orderData.companyName!.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
icon: AppAssets.insurance_active_icon,
|
||||
labelText: orderData.companyName!,
|
||||
iconColor: AppColors.successColor,
|
||||
backgroundColor: AppColors.successColor.withValues(alpha: 0.15),
|
||||
iconSize: 12.w,
|
||||
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w),
|
||||
),
|
||||
|
||||
// Policy Number
|
||||
if (orderData.insurancePolicyNo != null && orderData.insurancePolicyNo!.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Policy: ${orderData.insurancePolicyNo}",
|
||||
),
|
||||
|
||||
AppCustomChipWidget(
|
||||
labelText: "Doctor: ${orderData.doctorName ?? "N/A"}",
|
||||
),
|
||||
if (widget.projectName.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
labelText: widget.projectName,
|
||||
),
|
||||
if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Clinic: ${orderData.clinicName!}",
|
||||
),
|
||||
if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Date: ${DateFormat('MMM dd, yyyy').format(orderData.appointmentDate!)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// SizedBox(height: 12.h),
|
||||
//
|
||||
// // Additional Details Section
|
||||
// Container(
|
||||
// padding: EdgeInsets.all(12.h),
|
||||
// decoration: BoxDecoration(
|
||||
// color: AppColors.bgScaffoldColor,
|
||||
// borderRadius: BorderRadius.circular(12.r),
|
||||
// ),
|
||||
// child: Column(
|
||||
// children: [
|
||||
// _buildInfoRow(
|
||||
// "Doctor".needTranslation,
|
||||
// orderData.doctorName ?? "N/A",
|
||||
// ),
|
||||
// if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty)
|
||||
// _buildInfoRow(
|
||||
// "Clinic".needTranslation,
|
||||
// orderData.clinicName!,
|
||||
// ),
|
||||
// if (orderData.appointmentDate != null)
|
||||
// _buildInfoRow(
|
||||
// "Appointment Date".needTranslation,
|
||||
// DateFormat('MMM dd, yyyy').format(orderData.appointmentDate!),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 12.h),
|
||||
|
||||
// Divider
|
||||
Container(height: 1, color: AppColors.dividerColor),
|
||||
|
||||
// Summary Section
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummarySection(AncillaryOrderProcedureItem orderData) {
|
||||
final totalProcedures = orderData.ancillaryOrderProcDetailsList?.length ?? 0;
|
||||
final selectedCount = selectedProcedures.length;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"Procedures".needTranslation.toText12(
|
||||
color: AppColors.textColorLight,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
"$selectedCount of $totalProcedures selected".toText14(
|
||||
isBold: true,
|
||||
weight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
"Total Amount".needTranslation.toText12(
|
||||
color: AppColors.textColorLight,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
_getTotalAmount().toStringAsFixed(2).toText14(
|
||||
isBold: true,
|
||||
weight: FontWeight.bold,
|
||||
color: AppColors.primaryRedColor,
|
||||
),
|
||||
AppColors.textColorLight,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
//
|
||||
// _getTotalAmount().toStringAsFixed(2).toText14(
|
||||
// isBold: true,
|
||||
// weight: FontWeight.bold,
|
||||
// color: AppColors.primaryRedColor,
|
||||
// ),
|
||||
// SizedBox(width: 4.w),
|
||||
// "SAR".toText12(color: AppColors.textColorLight),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 8.h),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: "$label:".toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w600),
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: value.toText12(color: AppColors.textColor, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProceduresSection(AncillaryOrderProcedureItem orderData) {
|
||||
if (orderData.ancillaryOrderProcDetailsList == null || orderData.ancillaryOrderProcDetailsList!.isEmpty) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
// Group procedures by category
|
||||
final groupedProcedures = groupBy(
|
||||
orderData.ancillaryOrderProcDetailsList!,
|
||||
(AncillaryOrderProcDetail proc) => proc.procedureCategoryName ?? "Other",
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: groupedProcedures.entries.map((entry) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
entry.key.toText18(isBold: true),
|
||||
SizedBox(height: 12.h),
|
||||
...entry.value.map((procedure) => _buildProcedureCard(procedure)),
|
||||
SizedBox(height: 16.h),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProcedureCard(AncillaryOrderProcDetail procedure) {
|
||||
final isDisabled = _isProcedureDisabled(procedure);
|
||||
// final isDisabled = _isProcedureDisabled(procedure);
|
||||
final isSelected = _isProcedureSelected(procedure);
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: 0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 100.0,
|
||||
child: FadeInAnimation(
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
margin: EdgeInsets.only(bottom: 12.h),
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: isDisabled ? AppColors.greyColor : AppColors.whiteColor,
|
||||
borderRadius: 24.h,
|
||||
hasShadow: !isDisabled,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: isDisabled ? null : () => _toggleProcedureSelection(procedure),
|
||||
borderRadius: BorderRadius.circular(24.h),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.h)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isDisabled)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 8.w),
|
||||
child: Checkbox(
|
||||
value: isSelected,
|
||||
onChanged: (v) => _toggleProcedureSelection(procedure),
|
||||
activeColor: AppColors.primaryRedColor,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
(procedure.procedureName ?? "N/A").toText14(isBold: true, maxlines: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 3.h,
|
||||
runSpacing: 8.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
labelText: _getApprovalStatusText(procedure),
|
||||
// backgroundColor: ,
|
||||
),
|
||||
// if (procedure.procedureID != null)
|
||||
// AppCustomChipWidget(
|
||||
// labelText: "ID: ${procedure.procedureID}",
|
||||
// ),
|
||||
if (procedure.isCovered == true)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Covered".needTranslation,
|
||||
backgroundColor: AppColors.successColor.withValues(alpha: 0.1),
|
||||
textColor: AppColors.successColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
Container(height: 1, color: AppColors.dividerColor),
|
||||
SizedBox(height: 12.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"Price".needTranslation.toText10(color: AppColors.textColorLight),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
children: [
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
(procedure.patientShare ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600),
|
||||
AppColors.textColorLight,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"VAT (15%)".needTranslation.toText10(color: AppColors.textColorLight),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
children: [
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
(procedure.patientTaxAmount ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600),
|
||||
AppColors.textColorLight,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"Total".needTranslation.toText10(color: AppColors.textColorLight),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
children: [
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
(procedure.patientShareWithTax ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600),
|
||||
AppColors.textColorLight,
|
||||
13,
|
||||
isSaudiCurrency: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildStickyPaymentButton(orderData) {
|
||||
final isButtonEnabled = selectedProcedures.isNotEmpty;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 16.h),
|
||||
_buildSummarySection(orderData),
|
||||
SizedBox(height: 16.h),
|
||||
CustomButton(
|
||||
borderWidth: 0,
|
||||
backgroundColor: AppColors.infoLightColor,
|
||||
text: "Proceed to Payment".needTranslation,
|
||||
onPressed: () {
|
||||
// Navigate to payment page with selected procedures
|
||||
Navigator.of(context).push(
|
||||
CustomPageRoute(
|
||||
page: AncillaryOrderPaymentPage(
|
||||
appointmentNoVida: widget.appointmentNoVida,
|
||||
orderNo: widget.orderNo,
|
||||
projectID: widget.projectID,
|
||||
selectedProcedures: selectedProcedures,
|
||||
totalAmount: _getTotalAmount(),
|
||||
appointmentDate: orderData.appointmentDate,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
isDisabled: !isButtonEnabled,
|
||||
textColor: AppColors.whiteColor,
|
||||
borderRadius: 12.r,
|
||||
borderColor: Colors.transparent,
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
),
|
||||
SizedBox(height: 22.h),
|
||||
],
|
||||
).paddingSymmetrical(24.w, 0);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ToDoPage extends StatefulWidget {
|
||||
const ToDoPage({super.key});
|
||||
|
||||
@override
|
||||
State<ToDoPage> createState() => _ToDoPageState();
|
||||
}
|
||||
|
||||
class _ToDoPageState extends State<ToDoPage> {
|
||||
@override
|
||||
void initState() {
|
||||
final TodoSectionViewModel todoSectionViewModel = context.read<TodoSectionViewModel>();
|
||||
scheduleMicrotask(() async {
|
||||
await todoSectionViewModel.initializeTodoSectionViewModel();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildLoadingShimmer() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) {
|
||||
return AncillaryOrderCard(
|
||||
order: AncillaryOrderItem(),
|
||||
isLoading: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CollapsingListView(
|
||||
title: "ToDo List".needTranslation,
|
||||
isLeading: false,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 16.h),
|
||||
"Ancillary Orders".needTranslation.toText18(isBold: true),
|
||||
Consumer<TodoSectionViewModel>(
|
||||
builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) {
|
||||
return todoSectionViewModel.isAncillaryOrdersLoading
|
||||
? _buildLoadingShimmer()
|
||||
: AncillaryOrdersList(
|
||||
orders: todoSectionViewModel.patientAncillaryOrdersList,
|
||||
onCheckIn: (order) => log("Check-in for order: ${order.orderNo}"),
|
||||
onViewDetails: (order) async {
|
||||
Navigator.of(context).push(CustomPageRoute(
|
||||
page: AncillaryOrderDetailsList(
|
||||
appointmentNoVida: order.appointmentNo ?? 0,
|
||||
orderNo: order.orderNo ?? 0,
|
||||
projectID: order.projectID ?? 0,
|
||||
projectName: order.projectName ?? "",
|
||||
)));
|
||||
log("View details for order: ${order.orderNo}");
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(24.w, 0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,284 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
|
||||
|
||||
class AncillaryOrdersList extends StatelessWidget {
|
||||
final List<AncillaryOrderItem> orders;
|
||||
final Function(AncillaryOrderItem order)? onCheckIn;
|
||||
final Function(AncillaryOrderItem order)? onViewDetails;
|
||||
|
||||
const AncillaryOrdersList({
|
||||
super.key,
|
||||
required this.orders,
|
||||
this.onCheckIn,
|
||||
this.onViewDetails,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Show empty state
|
||||
if (orders.isEmpty) {
|
||||
return _buildDefaultEmptyState(context);
|
||||
}
|
||||
|
||||
// Show orders list
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: orders.length,
|
||||
separatorBuilder: (BuildContext context, int index) => SizedBox(height: 12.h),
|
||||
itemBuilder: (context, index) {
|
||||
final order = orders[index];
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 100.0,
|
||||
child: FadeInAnimation(
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
|
||||
child: AncillaryOrderCard(
|
||||
order: order,
|
||||
isLoading: false,
|
||||
onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null,
|
||||
onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultEmptyState(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40.h),
|
||||
child: Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 12.r,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Utils.getNoDataWidget(
|
||||
context,
|
||||
noDataText: "You don't have any ancillary orders yet.".needTranslation,
|
||||
isSmallWidget: true,
|
||||
width: 62.w,
|
||||
height: 62.h,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AncillaryOrderCard extends StatelessWidget {
|
||||
const AncillaryOrderCard({
|
||||
super.key,
|
||||
required this.order,
|
||||
this.isLoading = false,
|
||||
this.onCheckIn,
|
||||
this.onViewDetails,
|
||||
});
|
||||
|
||||
final AncillaryOrderItem order;
|
||||
final bool isLoading;
|
||||
final VoidCallback? onCheckIn;
|
||||
final VoidCallback? onViewDetails;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 12.h),
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 24.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header Row with Order Number and Date
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// if (!isLoading)
|
||||
// "Order #".needTranslation.toText14(
|
||||
// color: AppColors.textColorLight,
|
||||
// weight: FontWeight.w500,
|
||||
// ),
|
||||
// SizedBox(width: 4.w),
|
||||
// (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading),
|
||||
// ],
|
||||
// ),
|
||||
// if (order.orderDate != null || isLoading)
|
||||
// (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!))
|
||||
// .toText12(color: AppColors.textColorLight)
|
||||
// .toShimmer2(isShow: isLoading),
|
||||
// ],
|
||||
// ),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Doctor and Clinic Info
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!isLoading) ...[
|
||||
Image.network(
|
||||
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png",
|
||||
width: 40.w,
|
||||
height: 40.h,
|
||||
fit: BoxFit.cover,
|
||||
).circle(100.r),
|
||||
SizedBox(width: 12.w),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Doctor Name
|
||||
if (order.doctorName != null || isLoading)
|
||||
(isLoading ? "Dr. John Smith" : order.doctorName!)
|
||||
.toString()
|
||||
.toText14(isBold: true, maxlines: 2)
|
||||
.toShimmer2(isShow: isLoading),
|
||||
|
||||
SizedBox(height: 4.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Chips for Appointment Info and Status
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 3.h,
|
||||
runSpacing: 4.h,
|
||||
children: [
|
||||
// projectName
|
||||
if (order.projectName != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: order.projectName ?? '-',
|
||||
).toShimmer2(isShow: isLoading),
|
||||
// orderNo
|
||||
if (order.orderNo != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
// icon: AppAssets.calendar,
|
||||
labelText: "${"Order# :".needTranslation}${order.orderNo ?? '-'}",
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Appointment Date
|
||||
if (order.appointmentDate != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
icon: AppAssets.calendar,
|
||||
labelText:
|
||||
isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Appointment Number
|
||||
if (order.appointmentNo != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Invoice Number
|
||||
if (order.invoiceNo != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Queued Status
|
||||
if (order.isQueued == true || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Queued".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Check-in Available Status
|
||||
if (order.isCheckInAllow == true || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Check-in Ready".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Action Buttons
|
||||
Row(
|
||||
children: [
|
||||
// Check-in Button (if available)
|
||||
if (order.isCheckInAllow == true || isLoading)
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
text: "Check In".needTranslation,
|
||||
onPressed: () {
|
||||
if (isLoading) {
|
||||
return;
|
||||
} else if (onCheckIn != null) {
|
||||
onCheckIn!();
|
||||
}
|
||||
},
|
||||
backgroundColor: AppColors.primaryRedColor,
|
||||
borderColor: AppColors.primaryRedColor,
|
||||
textColor: AppColors.whiteColor,
|
||||
fontSize: 14.f,
|
||||
fontWeight: FontWeight.w500,
|
||||
borderRadius: 10.r,
|
||||
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
|
||||
height: 40.h,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
),
|
||||
|
||||
if (order.isCheckInAllow == true || isLoading) SizedBox(width: 8.w),
|
||||
|
||||
// View Details Button
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
text: "View Details".needTranslation,
|
||||
onPressed: () {
|
||||
if (isLoading) {
|
||||
return;
|
||||
} else if (onViewDetails != null) {
|
||||
onViewDetails!();
|
||||
}
|
||||
},
|
||||
backgroundColor: Color(0xffFEE9EA),
|
||||
borderColor: Color(0xffFEE9EA),
|
||||
textColor: Color(0xffED1C2B),
|
||||
fontSize: 14.f,
|
||||
fontWeight: FontWeight.w500,
|
||||
borderRadius: 10.r,
|
||||
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
|
||||
height: 40.h,
|
||||
iconSize: 15.h,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
|
||||
|
||||
class AncillaryProceduresList extends StatelessWidget {
|
||||
final List<AncillaryOrderItem> orders;
|
||||
final Function(AncillaryOrderItem order)? onCheckIn;
|
||||
final Function(AncillaryOrderItem order)? onViewDetails;
|
||||
|
||||
const AncillaryProceduresList({
|
||||
super.key,
|
||||
required this.orders,
|
||||
this.onCheckIn,
|
||||
this.onViewDetails,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Show empty state
|
||||
if (orders.isEmpty) {
|
||||
return _buildDefaultEmptyState(context);
|
||||
}
|
||||
|
||||
// Show orders list
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: orders.length,
|
||||
itemBuilder: (context, index) {
|
||||
final order = orders[index];
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 100.0,
|
||||
child: FadeInAnimation(
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
|
||||
child: AncillaryOrderCard(
|
||||
order: order,
|
||||
isLoading: false,
|
||||
onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null,
|
||||
onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultEmptyState(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40.h),
|
||||
child: Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 12.r,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Utils.getNoDataWidget(
|
||||
context,
|
||||
noDataText: "You don't have any ancillary orders yet.".needTranslation,
|
||||
isSmallWidget: true,
|
||||
width: 62.w,
|
||||
height: 62.h,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AncillaryOrderCard extends StatelessWidget {
|
||||
const AncillaryOrderCard({
|
||||
super.key,
|
||||
required this.order,
|
||||
this.isLoading = false,
|
||||
this.onCheckIn,
|
||||
this.onViewDetails,
|
||||
});
|
||||
|
||||
final AncillaryOrderItem order;
|
||||
final bool isLoading;
|
||||
final VoidCallback? onCheckIn;
|
||||
final VoidCallback? onViewDetails;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 12.h),
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 24.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header Row with Order Number and Date
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
"Order #".needTranslation.toText14(
|
||||
color: AppColors.textColorLight,
|
||||
weight: FontWeight.w500,
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
(isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading),
|
||||
],
|
||||
),
|
||||
if (order.orderDate != null || isLoading)
|
||||
(isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!))
|
||||
.toText12(color: AppColors.textColorLight)
|
||||
.toShimmer2(isShow: isLoading),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Doctor and Clinic Info
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Doctor Name
|
||||
if (order.doctorName != null || isLoading)
|
||||
(isLoading ? "Dr. John Smith" : order.doctorName!)
|
||||
.toString()
|
||||
.toText14(isBold: true, maxlines: 2)
|
||||
.toShimmer2(isShow: isLoading),
|
||||
|
||||
SizedBox(height: 4.h),
|
||||
|
||||
// Clinic Name
|
||||
if (order.clinicName != null || isLoading)
|
||||
(isLoading ? "Cardiology Clinic" : order.clinicName!)
|
||||
.toString()
|
||||
.toText12(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.greyTextColor,
|
||||
maxLine: 2,
|
||||
)
|
||||
.toShimmer2(isShow: isLoading),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Chips for Appointment Info and Status
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 3.h,
|
||||
runSpacing: 4.h,
|
||||
children: [
|
||||
// Appointment Date
|
||||
if (order.appointmentDate != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
icon: AppAssets.calendar,
|
||||
labelText:
|
||||
isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Appointment Number
|
||||
if (order.appointmentNo != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Invoice Number
|
||||
if (order.invoiceNo != null || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Queued Status
|
||||
if (order.isQueued == true || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Queued".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
|
||||
// Check-in Available Status
|
||||
if (order.isCheckInAllow == true || isLoading)
|
||||
AppCustomChipWidget(
|
||||
labelText: "Check-in Ready".needTranslation,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Action Buttons
|
||||
Row(
|
||||
children: [
|
||||
// Check-in Button (if available)
|
||||
if (order.isCheckInAllow == true || isLoading)
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
text: "Check In".needTranslation,
|
||||
onPressed: () {
|
||||
if (isLoading) {
|
||||
return;
|
||||
} else if (onCheckIn != null) {
|
||||
onCheckIn!();
|
||||
}
|
||||
},
|
||||
backgroundColor: AppColors.primaryRedColor,
|
||||
borderColor: AppColors.primaryRedColor,
|
||||
textColor: AppColors.whiteColor,
|
||||
fontSize: 14.f,
|
||||
fontWeight: FontWeight.w500,
|
||||
borderRadius: 10.r,
|
||||
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
|
||||
height: 40.h,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
),
|
||||
|
||||
if (order.isCheckInAllow == true || isLoading) SizedBox(width: 8.w),
|
||||
|
||||
// View Details Button
|
||||
Expanded(
|
||||
child: CustomButton(
|
||||
text: "View Details".needTranslation,
|
||||
onPressed: () {
|
||||
if (isLoading) {
|
||||
return;
|
||||
} else if (onViewDetails != null) {
|
||||
onViewDetails!();
|
||||
}
|
||||
},
|
||||
backgroundColor: Color(0xffFEE9EA),
|
||||
borderColor: Color(0xffFEE9EA),
|
||||
textColor: Color(0xffED1C2B),
|
||||
fontSize: 14.f,
|
||||
fontWeight: FontWeight.w500,
|
||||
borderRadius: 10.r,
|
||||
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
|
||||
height: 40.h,
|
||||
icon: AppAssets.arrow_forward,
|
||||
iconColor: AppColors.primaryRedColor,
|
||||
iconSize: 15.h,
|
||||
).toShimmer2(isShow: isLoading),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue