From 6f067d92d63c161cd725589918f74910f5d72766 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 4 Jan 2026 16:37:25 +0300 Subject: [PATCH] My invoices implemented --- lib/core/dependencies.dart | 5 + .../get_invoice_details_response_model.dart | 489 ++++++++++++++++++ .../get_invoices_list_response_model.dart | 88 ++++ .../my_invoices/my_invoices_repo.dart | 141 +++++ .../my_invoices/my_invoices_view_model.dart | 99 ++++ lib/main.dart | 4 + .../hmg_services/services_page.dart | 22 +- .../medical_file/medical_file_page.dart | 15 +- .../my_invoices/my_invoices_details_page.dart | 272 ++++++++++ .../my_invoices/my_invoices_list.dart | 116 +++++ .../widgets/invoice_list_card.dart | 151 ++++++ lib/widgets/appbar/collapsing_list_view.dart | 7 + 12 files changed, 1403 insertions(+), 6 deletions(-) create mode 100644 lib/features/my_invoices/models/get_invoice_details_response_model.dart create mode 100644 lib/features/my_invoices/models/get_invoices_list_response_model.dart create mode 100644 lib/features/my_invoices/my_invoices_repo.dart create mode 100644 lib/features/my_invoices/my_invoices_view_model.dart create mode 100644 lib/presentation/my_invoices/my_invoices_details_page.dart create mode 100644 lib/presentation/my_invoices/my_invoices_list.dart create mode 100644 lib/presentation/my_invoices/widgets/invoice_list_card.dart diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 489af59..c6c5554 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -34,6 +34,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; @@ -138,6 +140,7 @@ class AppDependencies { getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -271,5 +274,7 @@ class AppDependencies { getIt.registerLazySingleton(() => HealthProvider()); getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt())); + + getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); } } diff --git a/lib/features/my_invoices/models/get_invoice_details_response_model.dart b/lib/features/my_invoices/models/get_invoice_details_response_model.dart new file mode 100644 index 0000000..ef88623 --- /dev/null +++ b/lib/features/my_invoices/models/get_invoice_details_response_model.dart @@ -0,0 +1,489 @@ +class GetInvoiceDetailsResponseModel { + int? projectID; + int? doctorID; + num? grandTotal; + num? quantity; + num? total; + num? discount; + num? subTotal; + int? invoiceNo; + String? createdOn; + String? procedureID; + String? procedureName; + String? procedureNameN; + num? procedurePrice; + num? patientShare; + num? companyShare; + num? totalPatientShare; + num? totalCompanyShare; + num? totalShare; + num? discountAmount; + num? vATPercentage; + num? patientVATAmount; + num? companyVATAmount; + num? totalVATAmount; + num? price; + int? patientID; + String? patientIdentificationNo; + String? patientName; + String? patientNameN; + String? nationalityID; + String? doctorName; + String? doctorNameN; + int? clinicID; + String? clinicDescription; + String? clinicDescriptionN; + String? appointmentDate; + int? appointmentNo; + String? insuranceID; + int? companyID; + String? companyName; + String? companyNameN; + String? companyAddress; + String? companyAddressN; + String? companyGroupAddress; + String? groupName; + String? groupNameN; + String? patientAddress; + String? vATNo; + String? paymentDate; + String? projectName; + num? totalDiscount; + num? totalPatientShareWithQuantity; + String? legalName; + String? legalNameN; + num? advanceAdjustment; + String? patientCityName; + String? patientCityNameN; + String? doctorImageURL; + List? listConsultation; + + GetInvoiceDetailsResponseModel( + {this.projectID, + this.doctorID, + this.grandTotal, + this.quantity, + this.total, + this.discount, + this.subTotal, + this.invoiceNo, + this.createdOn, + this.procedureID, + this.procedureName, + this.procedureNameN, + this.procedurePrice, + this.patientShare, + this.companyShare, + this.totalPatientShare, + this.totalCompanyShare, + this.totalShare, + this.discountAmount, + this.vATPercentage, + this.patientVATAmount, + this.companyVATAmount, + this.totalVATAmount, + this.price, + this.patientID, + this.patientIdentificationNo, + this.patientName, + this.patientNameN, + this.nationalityID, + this.doctorName, + this.doctorNameN, + this.clinicID, + this.clinicDescription, + this.clinicDescriptionN, + this.appointmentDate, + this.appointmentNo, + this.insuranceID, + this.companyID, + this.companyName, + this.companyNameN, + this.companyAddress, + this.companyAddressN, + this.companyGroupAddress, + this.groupName, + this.groupNameN, + this.patientAddress, + this.vATNo, + this.paymentDate, + this.projectName, + this.totalDiscount, + this.totalPatientShareWithQuantity, + this.legalName, + this.legalNameN, + this.advanceAdjustment, + this.patientCityName, + this.patientCityNameN, + this.doctorImageURL, + this.listConsultation}); + + GetInvoiceDetailsResponseModel.fromJson(Map json) { + projectID = json['ProjectID']; + doctorID = json['DoctorID']; + grandTotal = json['GrandTotal']; + quantity = json['Quantity']; + total = json['Total']; + discount = json['Discount']; + subTotal = json['SubTotal']; + invoiceNo = json['InvoiceNo']; + createdOn = json['CreatedOn']; + procedureID = json['ProcedureID']; + procedureName = json['ProcedureName']; + procedureNameN = json['ProcedureNameN']; + procedurePrice = json['ProcedurePrice']; + patientShare = json['PatientShare']; + companyShare = json['CompanyShare']; + totalPatientShare = json['TotalPatientShare']; + totalCompanyShare = json['TotalCompanyShare']; + totalShare = json['TotalShare']; + discountAmount = json['DiscountAmount']; + vATPercentage = json['VATPercentage']; + patientVATAmount = json['PatientVATAmount']; + companyVATAmount = json['CompanyVATAmount']; + totalVATAmount = json['TotalVATAmount']; + price = json['Price']; + patientID = json['PatientID']; + patientIdentificationNo = json['PatientIdentificationNo']; + patientName = json['PatientName']; + patientNameN = json['PatientNameN']; + nationalityID = json['NationalityID']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + clinicID = json['ClinicID']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + appointmentDate = json['AppointmentDate']; + appointmentNo = json['AppointmentNo']; + insuranceID = json['InsuranceID']; + companyID = json['CompanyID']; + companyName = json['CompanyName']; + companyNameN = json['CompanyNameN']; + companyAddress = json['CompanyAddress']; + companyAddressN = json['CompanyAddressN']; + companyGroupAddress = json['CompanyGroupAddress']; + groupName = json['GroupName']; + groupNameN = json['GroupNameN']; + patientAddress = json['PatientAddress']; + vATNo = json['VATNo']; + paymentDate = json['PaymentDate']; + projectName = json['ProjectName']; + totalDiscount = json['TotalDiscount']; + totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity']; + legalName = json['LegalName']; + legalNameN = json['LegalNameN']; + advanceAdjustment = json['AdvanceAdjustment']; + patientCityName = json['PatientCityName']; + patientCityNameN = json['PatientCityNameN']; + doctorImageURL = json['DoctorImageURL']; + if (json['listConsultation'] != null) { + listConsultation = []; + json['listConsultation'].forEach((v) { + listConsultation!.add(new ListConsultation.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['DoctorID'] = this.doctorID; + data['GrandTotal'] = this.grandTotal; + data['Quantity'] = this.quantity; + data['Total'] = this.total; + data['Discount'] = this.discount; + data['SubTotal'] = this.subTotal; + data['InvoiceNo'] = this.invoiceNo; + data['CreatedOn'] = this.createdOn; + data['ProcedureID'] = this.procedureID; + data['ProcedureName'] = this.procedureName; + data['ProcedureNameN'] = this.procedureNameN; + data['ProcedurePrice'] = this.procedurePrice; + data['PatientShare'] = this.patientShare; + data['CompanyShare'] = this.companyShare; + data['TotalPatientShare'] = this.totalPatientShare; + data['TotalCompanyShare'] = this.totalCompanyShare; + data['TotalShare'] = this.totalShare; + data['DiscountAmount'] = this.discountAmount; + data['VATPercentage'] = this.vATPercentage; + data['PatientVATAmount'] = this.patientVATAmount; + data['CompanyVATAmount'] = this.companyVATAmount; + data['TotalVATAmount'] = this.totalVATAmount; + data['Price'] = this.price; + data['PatientID'] = this.patientID; + data['PatientIdentificationNo'] = this.patientIdentificationNo; + data['PatientName'] = this.patientName; + data['PatientNameN'] = this.patientNameN; + data['NationalityID'] = this.nationalityID; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['ClinicID'] = this.clinicID; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNo'] = this.appointmentNo; + data['InsuranceID'] = this.insuranceID; + data['CompanyID'] = this.companyID; + data['CompanyName'] = this.companyName; + data['CompanyNameN'] = this.companyNameN; + data['CompanyAddress'] = this.companyAddress; + data['CompanyAddressN'] = this.companyAddressN; + data['CompanyGroupAddress'] = this.companyGroupAddress; + data['GroupName'] = this.groupName; + data['GroupNameN'] = this.groupNameN; + data['PatientAddress'] = this.patientAddress; + data['VATNo'] = this.vATNo; + data['PaymentDate'] = this.paymentDate; + data['ProjectName'] = this.projectName; + data['TotalDiscount'] = this.totalDiscount; + data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity; + data['LegalName'] = this.legalName; + data['LegalNameN'] = this.legalNameN; + data['AdvanceAdjustment'] = this.advanceAdjustment; + data['PatientCityName'] = this.patientCityName; + data['PatientCityNameN'] = this.patientCityNameN; + data['DoctorImageURL'] = this.doctorImageURL; + if (this.listConsultation != null) { + data['listConsultation'] = + this.listConsultation!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ListConsultation { + int? projectID; + int? doctorID; + num? grandTotal; + int? quantity; + num? total; + num? discount; + num? subTotal; + int? invoiceNo; + String? createdOn; + String? procedureID; + String? procedureName; + String? procedureNameN; + num? procedurePrice; + num? patientShare; + num? companyShare; + num? totalPatientShare; + num? totalCompanyShare; + num? totalShare; + num? discountAmount; + num? vATPercentage; + num? patientVATAmount; + num? companyVATAmount; + num? totalVATAmount; + num? price; + int? patientID; + int? patientIdentificationNo; + String? patientName; + String? patientNameN; + String? nationalityID; + String? doctorName; + String? doctorNameN; + int? clinicID; + String? clinicDescription; + String? clinicDescriptionN; + String? appointmentDate; + dynamic appointmentNo; + dynamic insuranceID; + dynamic companyID; + String? companyName; + String? companyNameN; + String? companyAddress; + String? companyAddressN; + String? companyGroupAddress; + String? groupName; + String? groupNameN; + String? patientAddress; + String? vATNo; + String? paymentDate; + String? projectName; + num? totalDiscount; + num? totalPatientShareWithQuantity; + String? legalName; + String? legalNameN; + num? advanceAdjustment; + String? patientCityName; + String? patientCityNameN; + + ListConsultation( + {this.projectID, + this.doctorID, + this.grandTotal, + this.quantity, + this.total, + this.discount, + this.subTotal, + this.invoiceNo, + this.createdOn, + this.procedureID, + this.procedureName, + this.procedureNameN, + this.procedurePrice, + this.patientShare, + this.companyShare, + this.totalPatientShare, + this.totalCompanyShare, + this.totalShare, + this.discountAmount, + this.vATPercentage, + this.patientVATAmount, + this.companyVATAmount, + this.totalVATAmount, + this.price, + this.patientID, + this.patientIdentificationNo, + this.patientName, + this.patientNameN, + this.nationalityID, + this.doctorName, + this.doctorNameN, + this.clinicID, + this.clinicDescription, + this.clinicDescriptionN, + this.appointmentDate, + this.appointmentNo, + this.insuranceID, + this.companyID, + this.companyName, + this.companyNameN, + this.companyAddress, + this.companyAddressN, + this.companyGroupAddress, + this.groupName, + this.groupNameN, + this.patientAddress, + this.vATNo, + this.paymentDate, + this.projectName, + this.totalDiscount, + this.totalPatientShareWithQuantity, + this.legalName, + this.legalNameN, + this.advanceAdjustment, + this.patientCityName, + this.patientCityNameN}); + + ListConsultation.fromJson(Map json) { + projectID = json['ProjectID']; + doctorID = json['DoctorID']; + grandTotal = json['GrandTotal']; + quantity = json['Quantity']; + total = json['Total']; + discount = json['Discount']; + subTotal = json['SubTotal']; + invoiceNo = json['InvoiceNo']; + createdOn = json['CreatedOn']; + procedureID = json['ProcedureID']; + procedureName = json['ProcedureName']; + procedureNameN = json['ProcedureNameN']; + procedurePrice = json['ProcedurePrice']; + patientShare = json['PatientShare']; + companyShare = json['CompanyShare']; + totalPatientShare = json['TotalPatientShare']; + totalCompanyShare = json['TotalCompanyShare']; + totalShare = json['TotalShare']; + discountAmount = json['DiscountAmount']; + vATPercentage = json['VATPercentage']; + patientVATAmount = json['PatientVATAmount']; + companyVATAmount = json['CompanyVATAmount']; + totalVATAmount = json['TotalVATAmount']; + price = json['Price']; + patientID = json['PatientID']; + patientIdentificationNo = json['PatientIdentificationNo']; + patientName = json['PatientName']; + patientNameN = json['PatientNameN']; + nationalityID = json['NationalityID']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + clinicID = json['ClinicID']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + appointmentDate = json['AppointmentDate']; + appointmentNo = json['AppointmentNo']; + insuranceID = json['InsuranceID']; + companyID = json['CompanyID']; + companyName = json['CompanyName']; + companyNameN = json['CompanyNameN']; + companyAddress = json['CompanyAddress']; + companyAddressN = json['CompanyAddressN']; + companyGroupAddress = json['CompanyGroupAddress']; + groupName = json['GroupName']; + groupNameN = json['GroupNameN']; + patientAddress = json['PatientAddress']; + vATNo = json['VATNo']; + paymentDate = json['PaymentDate']; + projectName = json['ProjectName']; + totalDiscount = json['TotalDiscount']; + totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity']; + legalName = json['LegalName']; + legalNameN = json['LegalNameN']; + advanceAdjustment = json['AdvanceAdjustment']; + patientCityName = json['PatientCityName']; + patientCityNameN = json['PatientCityNameN']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['DoctorID'] = this.doctorID; + data['GrandTotal'] = this.grandTotal; + data['Quantity'] = this.quantity; + data['Total'] = this.total; + data['Discount'] = this.discount; + data['SubTotal'] = this.subTotal; + data['InvoiceNo'] = this.invoiceNo; + data['CreatedOn'] = this.createdOn; + data['ProcedureID'] = this.procedureID; + data['ProcedureName'] = this.procedureName; + data['ProcedureNameN'] = this.procedureNameN; + data['ProcedurePrice'] = this.procedurePrice; + data['PatientShare'] = this.patientShare; + data['CompanyShare'] = this.companyShare; + data['TotalPatientShare'] = this.totalPatientShare; + data['TotalCompanyShare'] = this.totalCompanyShare; + data['TotalShare'] = this.totalShare; + data['DiscountAmount'] = this.discountAmount; + data['VATPercentage'] = this.vATPercentage; + data['PatientVATAmount'] = this.patientVATAmount; + data['CompanyVATAmount'] = this.companyVATAmount; + data['TotalVATAmount'] = this.totalVATAmount; + data['Price'] = this.price; + data['PatientID'] = this.patientID; + data['PatientIdentificationNo'] = this.patientIdentificationNo; + data['PatientName'] = this.patientName; + data['PatientNameN'] = this.patientNameN; + data['NationalityID'] = this.nationalityID; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['ClinicID'] = this.clinicID; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNo'] = this.appointmentNo; + data['InsuranceID'] = this.insuranceID; + data['CompanyID'] = this.companyID; + data['CompanyName'] = this.companyName; + data['CompanyNameN'] = this.companyNameN; + data['CompanyAddress'] = this.companyAddress; + data['CompanyAddressN'] = this.companyAddressN; + data['CompanyGroupAddress'] = this.companyGroupAddress; + data['GroupName'] = this.groupName; + data['GroupNameN'] = this.groupNameN; + data['PatientAddress'] = this.patientAddress; + data['VATNo'] = this.vATNo; + data['PaymentDate'] = this.paymentDate; + data['ProjectName'] = this.projectName; + data['TotalDiscount'] = this.totalDiscount; + data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity; + data['LegalName'] = this.legalName; + data['LegalNameN'] = this.legalNameN; + data['AdvanceAdjustment'] = this.advanceAdjustment; + data['PatientCityName'] = this.patientCityName; + data['PatientCityNameN'] = this.patientCityNameN; + return data; + } +} diff --git a/lib/features/my_invoices/models/get_invoices_list_response_model.dart b/lib/features/my_invoices/models/get_invoices_list_response_model.dart new file mode 100644 index 0000000..e8056d9 --- /dev/null +++ b/lib/features/my_invoices/models/get_invoices_list_response_model.dart @@ -0,0 +1,88 @@ +class GetInvoicesListResponseModel { + String? setupId; + int? projectID; + int? patientID; + int? appointmentNo; + String? appointmentDate; + String? appointmentDateN; + int? clinicID; + int? doctorID; + int? invoiceNo; + int? status; + String? arrivedOn; + String? doctorName; + String? doctorNameN; + String? clinicName; + double? decimalDoctorRate; + String? doctorImageURL; + int? doctorRate; + int? patientNumber; + String? projectName; + + GetInvoicesListResponseModel( + {this.setupId, + this.projectID, + this.patientID, + this.appointmentNo, + this.appointmentDate, + this.appointmentDateN, + this.clinicID, + this.doctorID, + this.invoiceNo, + this.status, + this.arrivedOn, + this.doctorName, + this.doctorNameN, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorRate, + this.patientNumber, + this.projectName}); + + GetInvoicesListResponseModel.fromJson(Map json) { + setupId = json['SetupId']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + appointmentNo = json['AppointmentNo']; + appointmentDate = json['AppointmentDate']; + appointmentDateN = json['AppointmentDateN']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + invoiceNo = json['InvoiceNo']; + status = json['Status']; + arrivedOn = json['ArrivedOn']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + clinicName = json['ClinicName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorRate = json['DoctorRate']; + patientNumber = json['PatientNumber']; + projectName = json['ProjectName']; + } + + Map toJson() { + final Map data = {}; + data['SetupId'] = this.setupId; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentDateN'] = this.appointmentDateN; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['InvoiceNo'] = this.invoiceNo; + data['Status'] = this.status; + data['ArrivedOn'] = this.arrivedOn; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['ClinicName'] = this.clinicName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorRate'] = this.doctorRate; + data['PatientNumber'] = this.patientNumber; + data['ProjectName'] = this.projectName; + return data; + } +} diff --git a/lib/features/my_invoices/my_invoices_repo.dart b/lib/features/my_invoices/my_invoices_repo.dart new file mode 100644 index 0000000..68eee6e --- /dev/null +++ b/lib/features/my_invoices/my_invoices_repo.dart @@ -0,0 +1,141 @@ +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/my_invoices/models/get_invoice_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class MyInvoicesRepo { + Future>>> getAllInvoicesList(); + + Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}); + + Future>> sendInvoiceEmail({required num appointmentNo, required int projectID}); +} + +class MyInvoicesRepoImp implements MyInvoicesRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + MyInvoicesRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getAllInvoicesList() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DentalAppointments']; + + final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: invoicesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async { + Map mapDevice = { + "AppointmentNo": appointmentNo, + "InvoiceNo": invoiceNo, + "IsRegistered": true, + "ProjectID": projectID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_DENTAL_APPOINTMENT_INVOICE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_eInvoiceForDental']; + final invoicesList = GetInvoiceDetailsResponseModel.fromJson(list[0]); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: invoicesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> sendInvoiceEmail({required num appointmentNo, required int projectID}) async { + Map mapDevice = { + "AppointmentNo": appointmentNo, + "IsRegistered": true, + "ProjectID": projectID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart new file mode 100644 index 0000000..a02d741 --- /dev/null +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +class MyInvoicesViewModel extends ChangeNotifier { + bool isInvoicesListLoading = false; + bool isInvoiceDetailsLoading = false; + + MyInvoicesRepo myInvoicesRepo; + ErrorHandlerService errorHandlerService; + NavigationService navServices; + + List allInvoicesList = []; + late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel; + + MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices}); + + setInvoicesListLoading() { + isInvoicesListLoading = true; + allInvoicesList.clear(); + notifyListeners(); + } + + setInvoiceDetailLoading() { + isInvoiceDetailsLoading = true; + notifyListeners(); + } + + Future getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myInvoicesRepo.getAllInvoicesList(); + + result.fold( + (failure) async { + isInvoicesListLoading = false; + notifyListeners(); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + allInvoicesList = apiResponse.data!; + isInvoicesListLoading = false; + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID); + + result.fold( + (failure) async { + isInvoiceDetailsLoading = false; + notifyListeners(); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + invoiceDetailsResponseModel = apiResponse.data!; + isInvoiceDetailsLoading = false; + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID); + + result.fold( + (failure) async { + notifyListeners(); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 9600d5e..a9d16dc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,6 +25,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; @@ -169,6 +170,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 1380ad9..3b31d38 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/route_extensions.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/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_mo import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; @@ -48,9 +50,21 @@ class ServicesPage extends StatelessWidget { "".needTranslation, AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, - true, - route: AppRoutes.eReferralPage, - ), + true, route: null, onTap: () { + getIt.get().flushData(); + getIt.get().getTransportationOrders( + showLoader: false, + ); + getIt.get().getRRTOrders( + showLoader: false, + ); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( + CustomPageRoute( + page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), + ), + ); + }), HmgServicesComponentModel( 11, "Book\nAppointment".needTranslation, @@ -58,7 +72,7 @@ class ServicesPage extends StatelessWidget { AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, - route: AppRoutes.eReferralPage, + route: AppRoutes.bookAppointmentPage, ), HmgServicesComponentModel( 5, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index c9603b5..8ff71e3 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -22,6 +22,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_res import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; @@ -46,6 +47,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_fi import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -79,6 +81,7 @@ class _MedicalFilePageState extends State { late MedicalFileViewModel medicalFileViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late LabViewModel labViewModel; + late MyInvoicesViewModel myInvoicesViewModel; int currentIndex = 0; @@ -104,9 +107,11 @@ class _MedicalFilePageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); medicalFileViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); + myInvoicesViewModel = Provider.of(context, listen: false); NavigationService navigationService = getIt.get(); return CollapsingListView( - title: "Medical File".needTranslation, + // title: "Medical File".needTranslation, + title: LocaleKeys.medicalFile.tr(context: context), trailing: Row( children: [ Wrap( @@ -998,7 +1003,13 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.invoices_list_icon, isLargeText: true, iconSize: 36.w, - ), + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesList(), + ), + ); + }), MedicalFileCard( label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart new file mode 100644 index 0000000..cccd671 --- /dev/null +++ b/lib/presentation/my_invoices/my_invoices_details_page.dart @@ -0,0 +1,272 @@ +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/dependencies.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/my_invoices/models/get_invoice_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class MyInvoicesDetailsPage extends StatefulWidget { + GetInvoiceDetailsResponseModel getInvoiceDetailsResponseModel; + + MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel}); + + @override + State createState() => _MyInvoicesDetailsPageState(); +} + +class _MyInvoicesDetailsPageState extends State { + late MyInvoicesViewModel myInvoicesViewModel; + + @override + Widget build(BuildContext context) { + myInvoicesViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Invoice Details".needTranslation, + sendEmail: () async { + LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation); + await myInvoicesViewModel.sendInvoiceEmail( + appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!, + projectID: widget.getInvoiceDetailsResponseModel.projectID!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + Image.network( + widget.getInvoiceDetailsResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100.r), + ], + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.doctorNameN! : widget.getInvoiceDetailsResponseModel.doctorName!).toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.invoiceNo}: ${widget.getInvoiceDetailsResponseModel.invoiceNo!}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: (widget.getInvoiceDetailsResponseModel.clinicDescription!.length > 15 + ? '${widget.getInvoiceDetailsResponseModel.clinicDescription!.substring(0, 12)}...' + : widget.getInvoiceDetailsResponseModel.clinicDescription!), + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: widget.getInvoiceDetailsResponseModel.projectName!, + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Insurance Details".toText16(isBold: true), + SizedBox(height: 16.h), + widget.getInvoiceDetailsResponseModel.groupName!.toText14(isBold: true), + Row( + children: [ + Expanded(child: widget.getInvoiceDetailsResponseModel.companyName!.toText14(isBold: true)), + ], + ), + SizedBox(height: 12.h), + Row( + children: [ + AppCustomChipWidget( + labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + "Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Discount".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor), + AppColors.primaryRedColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Paid".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart new file mode 100644 index 0000000..ef1a9c2 --- /dev/null +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -0,0 +1,116 @@ +import 'dart:async'; + +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/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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.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/common_bottom_sheet.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 MyInvoicesList extends StatefulWidget { + const MyInvoicesList({super.key}); + + @override + State createState() => _MyInvoicesListState(); +} + +class _MyInvoicesListState extends State { + late MyInvoicesViewModel myInvoicesViewModel; + + @override + void initState() { + scheduleMicrotask(() { + myInvoicesViewModel.setInvoicesListLoading(); + myInvoicesViewModel.getAllInvoicesList(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + myInvoicesViewModel = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.invoiceList.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, myInvoicesVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + ListView.builder( + itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return myInvoicesVM.isInvoicesListLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : 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, + child: InvoiceListCard( + getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + onTap: () async { + myInvoicesVM.setInvoiceDetailLoading(); + LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation); + await myInvoicesVM.getInvoiceDetails( + appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectID!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel), + ), + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + ), + ), + ), + ), + ); + }).paddingSymmetrical(24.w, 0.h), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart new file mode 100644 index 0000000..27ca79a --- /dev/null +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -0,0 +1,151 @@ +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/dependencies.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/my_invoices/models/get_invoices_list_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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 InvoiceListCard extends StatelessWidget { + final GetInvoicesListResponseModel getInvoicesListResponseModel; + Function? onTap; + + InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + alignment: WrapAlignment.start, + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.walkin_appointment_icon, + iconColor: AppColors.textColor, + labelText: 'Walk In'.needTranslation, + textColor: AppColors.textColor, + ), + AppCustomChipWidget( + labelText: 'OutPatient'.needTranslation, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + textColor: AppColors.primaryRedColor, + ), + ], + ), + SizedBox(height: 16.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + Image.network( + getInvoicesListResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100.r), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, // Makes the container circular + border: Border.all( + color: AppColors.scaffoldBgColor, // Color of the border + width: 1.5.w, // Width of the border + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), + SizedBox(height: 2.h), + "${getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100), + ), + ], + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (getIt().isArabic() ? getInvoicesListResponseModel.doctorNameN! : getInvoicesListResponseModel.doctorName!).toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.invoiceNo}: ${getInvoicesListResponseModel.invoiceNo!}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: + (getInvoicesListResponseModel.clinicName!.length > 15 ? '${getInvoicesListResponseModel.clinicName!.substring(0, 12)}...' : getInvoicesListResponseModel.clinicName!), + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: getInvoicesListResponseModel.projectName!, + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false), + ), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.h), + CustomButton( + text: "View invoice details".needTranslation, + onPressed: () { + if (onTap != null) { + onTap!(); + } + }, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconSize: 14.h, + ), + ], + ), + ), + ).paddingOnly(bottom: 16.h); + } +} diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index c1ab6d4..aa6cc3e 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -21,6 +21,7 @@ class CollapsingListView extends StatelessWidget { VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; + VoidCallback? sendEmail; Widget? bottomChild; Widget? trailing; bool isClose; @@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget { this.history, this.instructions, this.requests, + this.sendEmail, this.isLeading = true, this.trailing, this.leadingCallback, @@ -78,6 +80,7 @@ class CollapsingListView extends StatelessWidget { history: history, instructions: instructions, requests: requests, + sendEmail: sendEmail, bottomChild: bottomChild, trailing: trailing, ) @@ -108,6 +111,7 @@ class CollapsingListView extends StatelessWidget { history: history, instructions: instructions, requests: requests, + sendEmail: sendEmail, bottomChild: bottomChild, trailing: trailing, ), @@ -181,6 +185,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; + VoidCallback? sendEmail; Widget? bottomChild; Widget? trailing; @@ -195,6 +200,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.history, this.instructions, this.requests, + this.sendEmail, this.bottomChild, this.trailing, }); @@ -267,6 +273,7 @@ class _ScrollAnimatedTitleState extends State { if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.sendEmail != null) actionButton(context, t, title: "Send Email".needTranslation, icon: AppAssets.email).onPress(widget.sendEmail!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), if (widget.trailing != null) widget.trailing!, ]