From f28762a5c55127e756490f9bc59cce6ebcf3ff29 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sun, 14 Dec 2025 10:01:05 +0300 Subject: [PATCH 1/2] last appointment rated done. --- lib/core/dependencies.dart | 4 + .../authentication/authentication_repo.dart | 2 +- .../appointment_rating_view_model.dart | 134 ++++++++++ .../req_model/appointment_rate_req_model.dart | 100 ++++++++ .../appointment_details_resp_model.dart | 64 +++++ .../rate_appointment_resp_model.dart | 160 ++++++++++++ .../my_appointments/my_appointments_repo.dart | 184 +++++++++++++- .../my_appointments_view_model.dart | 4 + lib/main.dart | 4 + .../widgets/appointment_card.dart | 7 +- lib/presentation/home/landing_page.dart | 50 +++- .../rate_appointment_clinic.dart | 213 ++++++++++++++++ .../rate_appointment_doctor.dart | 234 ++++++++++++++++++ .../rate_appointment/widget/doctor_row.dart | 96 +++++++ 14 files changed, 1243 insertions(+), 13 deletions(-) create mode 100644 lib/features/my_appointments/appointment_rating_view_model.dart create mode 100644 lib/features/my_appointments/models/req_model/appointment_rate_req_model.dart create mode 100644 lib/features/my_appointments/models/resp_models/appointment_details_resp_model.dart create mode 100644 lib/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart create mode 100644 lib/presentation/rate_appointment/rate_appointment_clinic.dart create mode 100644 lib/presentation/rate_appointment/rate_appointment_doctor.dart create mode 100644 lib/presentation/rate_appointment/widget/doctor_row.dart diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 2a5c749..cba52c6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -27,6 +27,7 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart'; import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +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_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -137,6 +138,9 @@ class AppDependencies { getIt.registerLazySingleton( () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton( + () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton( () => PayfortViewModel( payfortRepo: getIt(), diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 6ecf4b1..7fdec14 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -266,7 +266,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { newRequest.forRegisteration = newRequest.isRegister ?? false; newRequest.isRegister = false; //silent login case removed token and login token - if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true) { + if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true && (newRequest.loginType==1 || newRequest.loginType==4)) { newRequest.logInTokenID = null; newRequest.deviceToken = null; } diff --git a/lib/features/my_appointments/appointment_rating_view_model.dart b/lib/features/my_appointments/appointment_rating_view_model.dart new file mode 100644 index 0000000..a192df1 --- /dev/null +++ b/lib/features/my_appointments/appointment_rating_view_model.dart @@ -0,0 +1,134 @@ +// dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_details_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +import 'models/resp_models/rate_appointment_resp_model.dart'; + +class AppointmentRatingViewModel extends ChangeNotifier { + final MyAppointmentsRepo myAppointmentsRepo; + final ErrorHandlerService errorHandlerService; + final AppState appState; + List appointmentRatedList = []; + AppointmentDetails? appointmentDetails; + AppointmentRatingViewModel({ + required this.myAppointmentsRepo, + required this.errorHandlerService, + required this.appState, + }); + + + String title = ""; + String subTitle = ""; + bool isRateClinic = false; + + Future getLastRatingAppointment({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getLastRatingAppointment(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError?.call(apiResponse.errorMessage ?? 'Unknown error'); + } else if (apiResponse.messageStatus == 1) { + appointmentRatedList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future getAppointmentDetails(int appointmentID, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getAppointmentDetails(appointmentID, projectID); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError?.call(apiResponse.errorMessage ?? 'Unknown error'); + } else if (apiResponse.messageStatus == 1) { + appointmentDetails = apiResponse.data ?? AppointmentDetails(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + Future submitDoctorRating( {required int docRate, required String docNote,Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendDoctorRate( + docRate, + appointmentDetails!.appointmentNo!, + appointmentDetails!.projectID!, + appointmentDetails!.doctorID!, + appointmentDetails!.clinicID!, + docNote, + appointmentDetails!.appointmentDate!, + appointmentDetails!.doctorName, + appointmentDetails!.projectName, + appointmentDetails!.clinicName + ); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError?.call(apiResponse.errorMessage ?? 'Unknown error'); + } else if (apiResponse.messageStatus == 1) { + + notifyListeners(); + if (onSuccess != null) { + // onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future submitClinicRating( { required int clinicRate, required String clinicNote, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendAppointmentRate( + clinicRate, + appointmentDetails!.appointmentNo!, + appointmentDetails!.projectID!, + appointmentDetails!.doctorID!, + appointmentDetails!.clinicID!, + clinicNote + ); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError?.call(apiResponse.errorMessage ?? 'Unknown error'); + } else if (apiResponse.messageStatus == 1) { + + notifyListeners(); + if (onSuccess != null) { + // onSuccess(apiResponse.data); + } + } + }, + ); + } + + void setSubTitle(String value) { + this.subTitle = value; + notifyListeners(); + } + + void setTitle(String value) { + this.title = value; + notifyListeners(); + } + void setClinicOrDoctor(bool value){ + this.isRateClinic = value; + notifyListeners(); + } +} diff --git a/lib/features/my_appointments/models/req_model/appointment_rate_req_model.dart b/lib/features/my_appointments/models/req_model/appointment_rate_req_model.dart new file mode 100644 index 0000000..91070f2 --- /dev/null +++ b/lib/features/my_appointments/models/req_model/appointment_rate_req_model.dart @@ -0,0 +1,100 @@ +class AppointmentRate { + int? rate; + int? appointmentNo; + int? projectID; + int? doctorID; + int? clinicID; + String? note; + String? mobileNumber; + int? createdBy; + int? editedBy; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + + AppointmentRate( + {this.rate, + this.appointmentNo, + this.projectID, + this.doctorID, + this.clinicID, + this.note, + this.mobileNumber, + this.createdBy, + this.editedBy, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType}); + + AppointmentRate.fromJson(Map json) { + rate = json['Rate']; + appointmentNo = json['AppointmentNo']; + projectID = json['ProjectID']; + doctorID = json['DoctorID']; + clinicID = json['ClinicID']; + note = json['Note']; + mobileNumber = json['MobileNumber']; + createdBy = json['CreatedBy']; + editedBy = json['EditedBy']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + } + + Map toJson() { + final Map data = new Map(); + data['Rate'] = this.rate; + data['AppointmentNo'] = this.appointmentNo; + data['ProjectID'] = this.projectID; + data['DoctorID'] = this.doctorID; + data['ClinicID'] = this.clinicID; + data['Note'] = this.note; + data['MobileNumber'] = this.mobileNumber; + data['CreatedBy'] = this.createdBy; + data['EditedBy'] = this.editedBy; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientID'] = this.patientID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + return data; + } +} diff --git a/lib/features/my_appointments/models/resp_models/appointment_details_resp_model.dart b/lib/features/my_appointments/models/resp_models/appointment_details_resp_model.dart new file mode 100644 index 0000000..2900bce --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/appointment_details_resp_model.dart @@ -0,0 +1,64 @@ +class AppointmentDetails { + String? setupID; + int? projectID; + int? patientID; + int? appointmentNo; + int? clinicID; + int? doctorID; + dynamic startTime; + dynamic endTime; + dynamic appointmentDate; + dynamic clinicName; + dynamic doctorImageURL; + dynamic doctorName; + dynamic projectName; + + AppointmentDetails( + {this.setupID, + this.projectID, + this.patientID, + this.appointmentNo, + this.clinicID, + this.doctorID, + this.startTime, + this.endTime, + this.appointmentDate, + this.clinicName, + this.doctorImageURL, + this.doctorName, + this.projectName}); + + AppointmentDetails.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + appointmentNo = json['AppointmentNo']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + startTime = json['StartTime']; + endTime = json['EndTime']; + appointmentDate = json['AppointmentDate']; + clinicName = json['ClinicName']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + projectName = json['ProjectName']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['AppointmentNo'] = this.appointmentNo; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['StartTime'] = this.startTime; + data['EndTime'] = this.endTime; + data['AppointmentDate'] = this.appointmentDate; + data['ClinicName'] = this.clinicName; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['ProjectName'] = this.projectName; + return data; + } +} diff --git a/lib/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart b/lib/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart new file mode 100644 index 0000000..877b7bc --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart @@ -0,0 +1,160 @@ +class RateAppointmentRespModel { + String? setupID; + int? projectID; + int? appointmentNo; + String? appointmentDate; + String? appointmentDateN; + int? appointmentType; + String? bookDate; + int? patientType; + int? patientID; + int? clinicID; + int? doctorID; + String? endDate; + String? startTime; + String? endTime; + int? status; + int? visitType; + int? visitFor; + int? patientStatusType; + int? companyID; + int? bookedBy; + String? bookedOn; + int? confirmedBy; + String? confirmedOn; + int? arrivalChangedBy; + String? arrivedOn; + int? editedBy; + String? editedOn; + dynamic doctorName; + String? doctorNameN; + String? statusDesc; + String? statusDescN; + bool? vitalStatus; + dynamic vitalSignAppointmentNo; + int? episodeID; + String? doctorTitle; + bool? isAppoitmentLiveCare; + + RateAppointmentRespModel( + {this.setupID, + this.projectID, + this.appointmentNo, + this.appointmentDate, + this.appointmentDateN, + this.appointmentType, + this.bookDate, + this.patientType, + this.patientID, + this.clinicID, + this.doctorID, + this.endDate, + this.startTime, + this.endTime, + this.status, + this.visitType, + this.visitFor, + this.patientStatusType, + this.companyID, + this.bookedBy, + this.bookedOn, + this.confirmedBy, + this.confirmedOn, + this.arrivalChangedBy, + this.arrivedOn, + this.editedBy, + this.editedOn, + this.doctorName, + this.doctorNameN, + this.statusDesc, + this.statusDescN, + this.vitalStatus, + this.vitalSignAppointmentNo, + this.episodeID, + this.doctorTitle, + this.isAppoitmentLiveCare}); + + RateAppointmentRespModel.fromJson(Map json) { + try { + setupID = json['SetupID']; + projectID = json['ProjectID']; + appointmentNo = json['AppointmentNo']; + appointmentDate = json['AppointmentDate']; + appointmentDateN = json['AppointmentDateN']; + appointmentType = json['AppointmentType']; + bookDate = json['BookDate']; + patientType = json['PatientType']; + patientID = json['PatientID']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + endDate = json['EndDate']; + startTime = json['StartTime']; + endTime = json['EndTime']; + status = json['Status']; + visitType = json['VisitType']; + visitFor = json['VisitFor']; + patientStatusType = json['PatientStatusType']; + companyID = json['CompanyID']; + bookedBy = json['BookedBy']; + bookedOn = json['BookedOn']; + confirmedBy = json['ConfirmedBy']; + confirmedOn = json['ConfirmedOn']; + arrivalChangedBy = json['ArrivalChangedBy']; + arrivedOn = json['ArrivedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + statusDesc = json['StatusDesc']; + statusDescN = json['StatusDescN']; + vitalStatus = json['VitalStatus']; + vitalSignAppointmentNo = json['VitalSignAppointmentNo']; + episodeID = json['EpisodeID']; + doctorTitle = json['DoctorTitle']; + isAppoitmentLiveCare = json['IsAppoitmentLiveCare']; + } catch (e) { + print(e); + } + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentDateN'] = this.appointmentDateN; + data['AppointmentType'] = this.appointmentType; + data['BookDate'] = this.bookDate; + data['PatientType'] = this.patientType; + data['PatientID'] = this.patientID; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['EndDate'] = this.endDate; + data['StartTime'] = this.startTime; + data['EndTime'] = this.endTime; + data['Status'] = this.status; + data['VisitType'] = this.visitType; + data['VisitFor'] = this.visitFor; + data['PatientStatusType'] = this.patientStatusType; + data['CompanyID'] = this.companyID; + data['BookedBy'] = this.bookedBy; + data['BookedOn'] = this.bookedOn; + data['ConfirmedBy'] = this.confirmedBy; + data['ConfirmedOn'] = this.confirmedOn; + data['ArrivalChangedBy'] = this.arrivalChangedBy; + data['ArrivedOn'] = this.arrivedOn; + data['EditedBy'] = this.editedBy; + data['EditedOn'] = this.editedOn; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['StatusDesc'] = this.statusDesc; + data['StatusDescN'] = this.statusDescN; + data['VitalStatus'] = this.vitalStatus; + data['VitalSignAppointmentNo'] = this.vitalSignAppointmentNo; + data['EpisodeID'] = this.episodeID; + data['DoctorTitle'] = this.doctorTitle; + data['IsAppoitmentLiveCare'] = this.isAppoitmentLiveCare; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 87ec10d..72ed332 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -8,6 +8,7 @@ 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/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; @@ -15,6 +16,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'models/req_model/appointment_rate_req_model.dart'; +import 'models/resp_models/appointment_details_resp_model.dart'; + abstract class MyAppointmentsRepo { Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}); @@ -58,6 +62,15 @@ abstract class MyAppointmentsRepo { Future>> sendAskDocCallRequest( {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}); + + Future>>> getLastRatingAppointment(); + + Future>> getAppointmentDetails(int appointmentID, int projectID); + + + Future>> sendAppointmentRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note); + + Future>> sendDoctorRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -67,7 +80,8 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async { + Future>>> getPatientAppointments( + {required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "IsComingFromCOC": false, @@ -176,7 +190,9 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { "AppointmentNo": appointmentNo, "PaymentMethodName": paymentMethodName, "PaymentAmount": payedAmount == 0 ? "0" : payedAmount.toString(), - "PaymentDate": payedAmount == 0 ? "" : "/Date(${DateTime.now().millisecondsSinceEpoch})/", + "PaymentDate": payedAmount == 0 ? "" : "/Date(${DateTime + .now() + .millisecondsSinceEpoch})/", "PaymentReferenceNumber": payedAmount == 0 ? "" : paymentReference, "ProjectID": projectID, "PatientID": patientID, @@ -746,4 +762,166 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } -} + + @override + Future>>> getLastRatingAppointment() async { + Map mapDevice = {}; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post(IS_LAST_APPOINTMENT_RATED, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['IsLastAppoitmentRatedList']; + + final lstRatingAppointmentList = list.map((item) => RateAppointmentRespModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: lstRatingAppointmentList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: mapDevice); + 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>> getAppointmentDetails(int appointmentID, int projectID) async { + Map mapDevice = { + "AppointmentNumber": appointmentID, + "ProjectID": projectID, + }; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(GET_APPOINTMENT_DETAILS_BY_NO, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppointmentDetails']; + + final appointmentDetails = AppointmentDetails.fromJson(list); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentDetails, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: mapDevice); + 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>> sendAppointmentRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note) async { + + AppointmentRate appointmentRate = AppointmentRate(); + appointmentRate.rate = rate; + appointmentRate.appointmentNo = appointmentNo; + appointmentRate.projectID = projectID; + appointmentRate.doctorID = doctorID; + appointmentRate.clinicID = clinicID; + appointmentRate.note = note; + appointmentRate.createdBy = 2; + appointmentRate.editedBy = 2; + + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(NEW_RATE_APPOINTMENT_URL, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppointmentRated']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: list, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: appointmentRate.toJson()); + 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>> sendDoctorRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName) async { + Map request; + + request = { + "DoctorID": doctorID, + "Rate": rate, + "ClinicID": clinicID, + "ProjectID": projectID, + "AppointmentNo": appointmentNo, + "Note": note, + // "MobileNumber": authenticatedUserObject.user!.mobileNumber, + "AppointmentDate": appoDate, + "DoctorName": docName, + "ProjectName": projectName, + "COCTypeName": 1, + // "PatientName": authenticatedUserObject.user!.firstName! + " " + authenticatedUserObject.user!.lastName!, + // "PatientOutSA": authenticatedUserObject.user!.outSA, + // "PatientTypeID": authenticatedUserObject.user!.patientType, + "ClinicName": clinicName, + // "PatientIdentificationID": authenticatedUserObject.user!.patientIdentificationNo + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(NEW_RATE_DOCTOR_URL, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppointmentRated']; + + // final appointmentDetails = AppointmentDetails.fromJson(list); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: list, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: request); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + +} \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index fa1ad22..0958c8e 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -37,6 +37,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { DateTime? start = null; DateTime? end = null; + + + List patientAppointmentsHistoryList = []; List filteredAppointmentList = []; @@ -679,4 +682,5 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + } diff --git a/lib/main.dart b/lib/main.dart index 1af80b6..c8fa667 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.d import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +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/payfort/payfort_view_model.dart'; @@ -104,6 +105,9 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), ), diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 3cf56a6..61174c0 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -38,7 +38,7 @@ class AppointmentCard extends StatelessWidget { final MedicalFileViewModel? medicalFileViewModel; final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; - + final bool isForRate; const AppointmentCard({ super.key, required this.patientAppointmentHistoryResponseModel, @@ -51,6 +51,7 @@ class AppointmentCard extends StatelessWidget { this.isForFeedback = false, this.medicalFileViewModel, this.contactUsViewModel, + this.isForRate =false }); @override @@ -63,11 +64,11 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(context, appState), + isForRate ? SizedBox(): _buildHeader(context, appState), SizedBox(height: 16.h), _buildDoctorRow(context), SizedBox(height: 16.h), - _buildActionArea(context, appState), + isForRate ? SizedBox(): _buildActionArea(context, appState), ], ), ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 1007547..6e0de4c 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_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/prescriptions/prescriptions_view_model.dart'; @@ -39,6 +40,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; +import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -64,7 +66,7 @@ class _LandingPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; late PrescriptionsViewModel prescriptionsViewModel; final CacheService cacheService = GetIt.instance(); - + late AppointmentRatingViewModel appointmentRatingViewModel; late InsuranceViewModel insuranceViewModel; late ImmediateLiveCareViewModel immediateLiveCareViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; @@ -76,6 +78,7 @@ class _LandingPageState extends State { void initState() { authVM = context.read(); habibWalletVM = context.read(); + appointmentRatingViewModel = context.read(); // myAppointmentsViewModel = context.read(); // prescriptionsViewModel = context.read(); // insuranceViewModel = context.read(); @@ -99,6 +102,17 @@ class _LandingPageState extends State { immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); emergencyServicesViewModel.checkPatientERAdvanceBalance(); + appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { + if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, + onSuccess: ((response) { + appointmentRatingViewModel.setClinicOrDoctor(false); + appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation); + appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation); + openLastRating(); + })); + } + }); } }); super.initState(); @@ -271,17 +285,14 @@ class _LandingPageState extends State { ) : Container( width: double.infinity, - decoration: RoundedRectangleBorder() - .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( padding: EdgeInsets.all(12.h), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment" - .needTranslation - .toText12(isCenter: true), + "You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -595,4 +606,31 @@ class _LandingPageState extends State { }, ); } + + openLastRating() { + showCommonBottomSheetWithoutHeight( + context, + titleWidget: Selector( + selector: (_, vm) => vm.title, + builder: (context, title, child) { + final displayTitle = title ?? ''; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + displayTitle.toText20(weight: FontWeight.w600), + (context.select((vm) => vm.subTitle) ?? '').toText12(), + ], + ); + }, + ), + isCloseButtonVisible: true, + child: StatefulBuilder( + builder: (context, setState) { + + return RateAppointmentDoctor(); + }, + ), + isFullScreen: false, + ); + } } diff --git a/lib/presentation/rate_appointment/rate_appointment_clinic.dart b/lib/presentation/rate_appointment/rate_appointment_clinic.dart new file mode 100644 index 0000000..a33628e --- /dev/null +++ b/lib/presentation/rate_appointment/rate_appointment_clinic.dart @@ -0,0 +1,213 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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/my_appointments/appointment_rating_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.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/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class RateAppointmentClinic extends StatefulWidget { + + late final String? doctorNote; + late final int? doctorRate; + + RateAppointmentClinic({this.doctorRate, this.doctorNote}); + + @override + _RateAppointmentClinicState createState() => _RateAppointmentClinicState(); +} + +class _RateAppointmentClinicState extends State { + final formKey = GlobalKey(); + String note = ""; + int rating = 5; + AppointmentRatingViewModel? appointmentRatingViewModel; + MyAppointmentsViewModel? myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + appointmentRatingViewModel = Provider.of(context, listen: false); + + // Make the sheet a fixed height and keep content scrollable while pinning buttons to bottom + final sheetHeight = ResponsiveExtension.screenHeight * 0.60; + + return SizedBox( + height: sheetHeight, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Scrollable content + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor row + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: BuildDoctorRow( + isForClinic: true, + appointmentDetails: appointmentRatingViewModel!.appointmentDetails, + ), + ), + SizedBox(height: 16), + + // Rate clinic box + SizedBox( + width: double.infinity, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Rate Clinic", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.64, height: 23 / 16), + ), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...List.generate( + 5, + (index) => rating == (index + 1) + ? Container( + margin: EdgeInsets.only(left: 3.0, right: 3.0), + child: IconButton( + onPressed: () { + setState(() { + rating = index + 1; + }); + }, + iconSize: 35, + icon: SvgPicture.asset('assets/images/svg/rate_${index + 1}.svg', colorFilter: getColors(rating)), + ), + ) + : IconButton( + onPressed: () { + setState(() { + rating = index + 1; + }); + }, + iconSize: 35, + icon: SvgPicture.asset('assets/images/svg/rate_${index + 1}.svg'), + ), + ), + ], + ), + ], + ), + ), + ), + ), + + SizedBox(height: 12), + + // Extra content area (keeps any other widgets that were previously below) + Container( + padding: EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Placeholder for in-content widgets if needed in future + ], + ), + ), + + // Add bottom spacing so last content isn't obscured by the fixed buttons + SizedBox(height: 12), + ], + ), + ), + + ), + + // Bottom action buttons pinned to bottom of the sheet + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric( vertical: 12.0), + child: Row( + children: [ + Expanded( + child: CustomButton( + text: "Back".needTranslation, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + onPressed: () { + appointmentRatingViewModel!.setTitle("Rate Doctor".needTranslation); + appointmentRatingViewModel!.setSubTitle("How was your last visit with doctor?".needTranslation); + appointmentRatingViewModel!.setClinicOrDoctor(false); + setState(() { + + }); + }, + ), + ), + SizedBox(width: 10), + Expanded( + child: CustomButton( + text: "Submit".needTranslation, + onPressed: () { + + submitRating(); + + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + ColorFilter getColors(int rating){ + + switch(rating){ + case 5: + return ColorFilter.mode(AppColors.bgGreenColor, BlendMode.srcIn); + case 4: + return ColorFilter.mode(Colors.greenAccent, BlendMode.srcIn); + case 3: + return ColorFilter.mode(AppColors.warningLightColor, BlendMode.srcIn); + case 2: + return ColorFilter.mode(Colors.orange, BlendMode.srcIn); + case 1: + return ColorFilter.mode(AppColors.primaryRedColor, BlendMode.srcIn); + + default: + return ColorFilter.mode(AppColors.greyColor, BlendMode.srcIn); + } + } + + submitRating() async{ + LoaderBottomSheet.showLoader(); + await appointmentRatingViewModel!.submitDoctorRating(docRate: widget.doctorRate!, docNote: widget.doctorNote!); + await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note); + LoaderBottomSheet.hideLoader(); + Navigator.pop(context); + } + +} diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart new file mode 100644 index 0000000..4bff683 --- /dev/null +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -0,0 +1,234 @@ +import 'package:flutter/cupertino.dart'; +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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_clinic.dart'; +import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.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/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; + +class RateAppointmentDoctor extends StatefulWidget { + + bool isFromRegistration; + + RateAppointmentDoctor({Key? key, this.isFromRegistration = false}) : super(key: key); + + @override + _RateAppointmentDoctorState createState() => _RateAppointmentDoctorState(); +} + +class _RateAppointmentDoctorState extends State { + final formKey = GlobalKey(); + String note = ""; + int rating = 5; + + // ProjectViewModel? projectViewModel; + AppointmentRatingViewModel? appointmentRatingViewModel; + MyAppointmentsViewModel? myAppointmentsViewModel; + + @override + void initState() { + + super.initState(); + } + + + @override + Widget build(BuildContext context) { + + myAppointmentsViewModel = Provider.of(context, listen: false); + appointmentRatingViewModel = Provider.of(context, listen: false); + + final sheetHeight = ResponsiveExtension.screenHeight * 0.60; + + return Selector( + selector: (_, vm) => vm.isRateClinic, + builder: (context, isRateClinic, child) => isRateClinic + ? RateAppointmentClinic(doctorNote: note, doctorRate: rating,) + : SizedBox( + height: sheetHeight, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Scrollable main content + Expanded( + + child: Padding( + padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor row + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: BuildDoctorRow( + isForClinic: false, + appointmentDetails: appointmentRatingViewModel!.appointmentDetails, + )), + + SizedBox(height: 16), + + // Rating box + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Please rate the doctor", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xff2B353E), + letterSpacing: -0.64, + height: 23 / 16), + ), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...List.generate( + 5, + (index) => AnimatedSwitcher( + duration: Duration(milliseconds: 1000), + switchInCurve: Curves.elasticOut, + switchOutCurve: Curves.elasticIn, + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(child: child, scale: animation); + }, + child: Container( + key: ValueKey(rating), + child: IconButton( + iconSize: 45.0, + onPressed: () { + setState(() { + rating = index + 1; + }); + }, + color: rating >= (index + 1) + ? Color.fromRGBO(255, 186, 0, 1.0) + : Colors.grey[400], + icon: Icon(rating >= (index + 1) ? Icons.star : Icons.star)), + ), + ), + ) + ], + ), + ], + ), + ), + ), + + SizedBox(height: 12), + + // Note text field + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.0), + child: TextField( + + maxLines: 5, + decoration: InputDecoration.collapsed( + hintText: "Notes".needTranslation, + hintStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xff2B353E), + letterSpacing: -0.64, + height: 23 / 16)), + onChanged: (value) { + setState(() { + note = value; + }); + }, + ))), + + SizedBox(height: 12), + + // Optional extra spacing so content doesn't touch buttons + SizedBox(height: 12), + ], + ), + ), + + ), + + // Bottom action buttons pinned to bottom + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Row( + children: [ + Expanded( + child: CustomButton( + text: "Later".needTranslation, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + SizedBox(width: 10), + Expanded( + child: CustomButton( + text: "Next".needTranslation, + onPressed: () { + // Set up clinic rating and show clinic rating view + appointmentRatingViewModel!.setTitle("Rate Clinic".needTranslation); + appointmentRatingViewModel!.setSubTitle("How was your appointment?".needTranslation); + appointmentRatingViewModel!.setClinicOrDoctor(true); + + setState(() {}); + }, + ), + ), + ], + ), + ), + ), + ], + ), + )); + + // DoctorList getDoctorObject(AppointmentRateViewModel model) { + // DoctorList doctor = new DoctorList(); + // + // doctor.name = model.appointmentDetails.doctorName; + // doctor.doctorImageURL = model.appointmentDetails.doctorImageURL; + // doctor.clinicName = model.appointmentDetails.clinicName; + // doctor.projectName = model.appointmentDetails.projectName; + // doctor.date = model.appointmentDetails.appointmentDate; + // doctor.actualDoctorRate = 5; + // + // return doctor; + // } + } + + +} diff --git a/lib/presentation/rate_appointment/widget/doctor_row.dart b/lib/presentation/rate_appointment/widget/doctor_row.dart new file mode 100644 index 0000000..f38e15d --- /dev/null +++ b/lib/presentation/rate_appointment/widget/doctor_row.dart @@ -0,0 +1,96 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_details_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class BuildDoctorRow extends StatelessWidget { + bool isForClinic = false; + AppointmentDetails? appointmentDetails; + + BuildDoctorRow({super.key, required this.isForClinic, this.appointmentDetails}); + + @override + Widget build(BuildContext context) { + + return Padding(padding: EdgeInsets.all(16),child:Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + isForClinic ? 'https://hmgwebservices.com/Images/Hospitals/${appointmentDetails!.projectID}.jpg' : appointmentDetails!.doctorImageURL , + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (isForClinic ? appointmentDetails!.projectName : appointmentDetails!.doctorName)!.toString() + .toText16(isBold: true, maxlines: 1), + + SizedBox(height: 8.h), + + + isForClinic ? Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + + labelText: + appointmentDetails!.clinicName.toString(), + + ), + AppCustomChipWidget( + icon: AppAssets.ic_date_filter, + labelText: + DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointmentDetails!.appointmentDate), false), + + ), + + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, + labelText: + appointmentDetails!.startTime.substring(0, appointmentDetails!.startTime.length - 3), + + ), + + ] + ) : Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + + labelText: + appointmentDetails!.projectName.toString(), + + + ), + AppCustomChipWidget( + + labelText: + appointmentDetails!.clinicName.toString(), + + ) + + ] + ) + ], + ), + ), + ], + )); + } + +} \ No newline at end of file From 1d49308cc73cc085b681b3e166d952ab7eedadbc Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sun, 14 Dec 2025 14:56:14 +0300 Subject: [PATCH 2/2] last appointment rated. --- .../rate_appointment_clinic.dart | 7 +++---- .../rate_appointment_doctor.dart | 17 ++++------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/presentation/rate_appointment/rate_appointment_clinic.dart b/lib/presentation/rate_appointment/rate_appointment_clinic.dart index a33628e..0913297 100644 --- a/lib/presentation/rate_appointment/rate_appointment_clinic.dart +++ b/lib/presentation/rate_appointment/rate_appointment_clinic.dart @@ -78,10 +78,9 @@ class _RateAppointmentClinicState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Rate Clinic", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.64, height: 23 / 16), - ), + + "Rate Clinic".needTranslation.toText16(isBold: true), + SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index 4bff683..782927d 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -92,15 +92,9 @@ class _RateAppointmentDoctorState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Please rate the doctor", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xff2B353E), - letterSpacing: -0.64, - height: 23 / 16), - ), + + "Please rate the doctor".needTranslation.toText16(isBold: true), + SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -150,7 +144,7 @@ class _RateAppointmentDoctorState extends State { padding: EdgeInsets.all(16.0), child: TextField( - maxLines: 5, + maxLines: 4, decoration: InputDecoration.collapsed( hintText: "Notes".needTranslation, hintStyle: TextStyle( @@ -166,10 +160,7 @@ class _RateAppointmentDoctorState extends State { }, ))), - SizedBox(height: 12), - // Optional extra spacing so content doesn't touch buttons - SizedBox(height: 12), ], ), ),