diff --git a/cypress/e2e/facility_spec/facility_creation.cy.ts b/cypress/e2e/facility_spec/facility_creation.cy.ts index 1937ddcd4ca..7c7111af006 100644 --- a/cypress/e2e/facility_spec/facility_creation.cy.ts +++ b/cypress/e2e/facility_spec/facility_creation.cy.ts @@ -1,6 +1,6 @@ -import { FacilityCreation } from "../../pageObject/facility/FacilityCreation"; -import { generatePhoneNumber } from "../../utils/commonUtils"; -import { generateFacilityData } from "../../utils/facilityData"; +import { FacilityCreation } from "@/cypress/pageObject/facility/FacilityCreation"; +import { generatePhoneNumber } from "@/cypress/utils/commonUtils"; +import { generateFacilityData } from "@/cypress/utils/facilityData"; const LOCATION_HIERARCHY = { localBody: "Aluva", diff --git a/cypress/e2e/patient_spec/patient_creation.cy.ts b/cypress/e2e/patient_spec/patient_creation.cy.ts index ea1e7abc30c..8bb6a30b46c 100644 --- a/cypress/e2e/patient_spec/patient_creation.cy.ts +++ b/cypress/e2e/patient_spec/patient_creation.cy.ts @@ -7,7 +7,7 @@ import { generateAddress, generateName, generatePhoneNumber, -} from "../../utils/commonUtils"; +} from "@/cypress/utils/commonUtils"; const facilityCreation = new FacilityCreation(); const ENCOUNTER_TYPE = "Observation"; diff --git a/public/locale/en.json b/public/locale/en.json index e7ab17781fc..50d7be8b564 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1770,6 +1770,7 @@ "required_quantity": "Required Quantity", "reschedule": "Reschedule", "reschedule_appointment": "Reschedule Appointment", + "reschedule_appointment_with": "Reschedule Appointment with", "rescheduled": "Rescheduled", "rescheduling": "Rescheduling...", "resend_otp": "Resend OTP", diff --git a/src/Routers/PatientRouter.tsx b/src/Routers/PatientRouter.tsx index cc07a27d887..5d2813bd372 100644 --- a/src/Routers/PatientRouter.tsx +++ b/src/Routers/PatientRouter.tsx @@ -47,6 +47,21 @@ const AppointmentRoutes = { facilityId: string; staffId: string; }) => , + "/facility/:facilityId/appointments/:staffId/reschedule/:appointmentId": ({ + facilityId, + staffId, + appointmentId, + }: { + facilityId: string; + staffId: string; + appointmentId: string; + }) => ( + + ), "/facility/:facilityId/appointments/:staffId/patient-select": ({ facilityId, staffId, diff --git a/src/pages/Patient/components/AppointmentDialog.tsx b/src/pages/Patient/components/AppointmentDialog.tsx index f9923f7181f..8b486ad901b 100644 --- a/src/pages/Patient/components/AppointmentDialog.tsx +++ b/src/pages/Patient/components/AppointmentDialog.tsx @@ -1,4 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { navigate } from "raviger"; import { Dispatch, SetStateAction } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -39,6 +40,11 @@ function AppointmentDialog({ const queryClient = useQueryClient(); const patient = usePatientContext(); const tokenData = patient?.tokenData; + const handleRescheduleAppointment = (appointment: Appointment) => { + navigate( + `/facility/${appointment.facility.id}/appointments/${appointment.user.id}/reschedule/${appointment.id}`, + ); + }; const { mutate: cancelAppointment, isPending } = useMutation({ mutationFn: mutate(PublicAppointmentApi.cancelAppointment, { headers: { @@ -103,7 +109,10 @@ function AppointmentDialog({ > {t("cancel")} - diff --git a/src/pages/PublicAppointments/Schedule.tsx b/src/pages/PublicAppointments/Schedule.tsx index 5afeb4f6907..f7fae60b9b2 100644 --- a/src/pages/PublicAppointments/Schedule.tsx +++ b/src/pages/PublicAppointments/Schedule.tsx @@ -1,6 +1,7 @@ -import { useQuery } from "@tanstack/react-query"; -import { format } from "date-fns"; -import { Link, navigate } from "raviger"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { format, isBefore, isSameDay } from "date-fns"; +import { Loader2 } from "lucide-react"; +import { navigate } from "raviger"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -19,29 +20,36 @@ import { Avatar } from "@/components/Common/Avatar"; import Loading from "@/components/Common/Loading"; import { FacilityModel } from "@/components/Facility/models"; +import useAppHistory from "@/hooks/useAppHistory"; import { usePatientContext } from "@/hooks/usePatientUser"; import routes from "@/Utils/request/api"; +import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; -import request from "@/Utils/request/request"; -import { RequestResult } from "@/Utils/request/types"; import { dateQueryString } from "@/Utils/utils"; import { groupSlotsByAvailability } from "@/pages/Appointments/utils"; import PublicAppointmentApi from "@/types/scheduling/PublicAppointmentApi"; -import { TokenSlot } from "@/types/scheduling/schedule"; +import { + Appointment, + AppointmentCreateRequest, + TokenSlot, +} from "@/types/scheduling/schedule"; interface AppointmentsProps { facilityId: string; staffId: string; + appointmentId?: string; } export function ScheduleAppointment(props: AppointmentsProps) { const { t } = useTranslation(); - const { facilityId, staffId } = props; + const { goBack } = useAppHistory(); + const { facilityId, staffId, appointmentId } = props; const [selectedMonth, setSelectedMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedSlot, setSelectedSlot] = useState(); const [reason, setReason] = useState(""); + const queryClient = useQueryClient(); const patientUserContext = usePatientContext(); const tokenData = patientUserContext?.tokenData; @@ -54,16 +62,34 @@ export function ScheduleAppointment(props: AppointmentsProps) { navigate(`/facility/${facilityId}/appointments/${staffId}/otp/send`); } - const { data: facilityResponse, error: facilityError } = useQuery< - RequestResult - >({ - queryKey: ["facility", facilityId], - queryFn: () => - request(routes.getAnyFacility, { + const { data: appointmentData } = useQuery<{ results: Appointment[] }>({ + queryKey: ["appointment", tokenData?.phoneNumber], + queryFn: query(PublicAppointmentApi.getAppointments, { + headers: { + Authorization: `Bearer ${tokenData?.token}`, + }, + }), + enabled: !!appointmentId && !!tokenData?.token, + }); + + const appointment = appointmentData?.results.find( + (appointment) => appointment.id === appointmentId, + ); + + useEffect(() => { + if (appointment) { + setReason(appointment.reason_for_visit); + } + }, [appointment]); + + const { data: facilityResponse, error: facilityError } = + useQuery({ + queryKey: ["facility", facilityId], + queryFn: query(routes.getAnyFacility, { pathParams: { id: facilityId }, silent: true, }), - }); + }); if (facilityError) { toast.error(t("error_fetching_facility_data")); @@ -109,6 +135,56 @@ export function ScheduleAppointment(props: AppointmentsProps) { } } + const { mutate: createAppointment, isPending: isCreatingAppointment } = + useMutation({ + mutationFn: (body: AppointmentCreateRequest) => + mutate(PublicAppointmentApi.createAppointment, { + pathParams: { id: selectedSlot?.id || "" }, + body, + headers: { + Authorization: `Bearer ${tokenData.token}`, + }, + })(body), + onSuccess: (data: Appointment) => { + toast.success(t("appointment_created_success")); + queryClient.invalidateQueries({ + queryKey: [ + ["patients", tokenData.phoneNumber], + ["appointment", tokenData.phoneNumber], + ], + }); + navigate(`/facility/${facilityId}/appointments/${data.id}/success`, { + replace: true, + }); + }, + }); + + const { mutate: cancelAppointment, isPending: isCancellingAppointment } = + useMutation({ + mutationFn: mutate(PublicAppointmentApi.cancelAppointment, { + headers: { + Authorization: `Bearer ${tokenData.token}`, + }, + }), + onSuccess: (appointment: Appointment) => { + toast.success(t("appointment_cancelled")); + queryClient.invalidateQueries({ + queryKey: ["appointment", tokenData.phoneNumber], + }); + createAppointment({ + reason_for_visit: reason, + patient: appointment.patient.id, + }); + }, + }); + + const handleRescheduleAppointment = (appointment: Appointment) => { + cancelAppointment({ + appointment: appointment.id, + patient: appointment.patient.id, + }); + }; + useEffect(() => { setSelectedSlot(undefined); }, [selectedDate]); @@ -139,12 +215,10 @@ export function ScheduleAppointment(props: AppointmentsProps) {
@@ -178,7 +252,7 @@ export function ScheduleAppointment(props: AppointmentsProps) {
- {facilityResponse?.data?.name} + {facilityResponse?.name}
@@ -188,7 +262,9 @@ export function ScheduleAppointment(props: AppointmentsProps) {
- {t("book_an_appointment_with")}{" "} + {appointmentId + ? t("reschedule_appointment_with") + : t("book_an_appointment_with")}{" "} {userData.user_type === "doctor" ? `Dr. ${userData.first_name} ${userData.last_name}` : `${userData.first_name} ${userData.last_name}`} @@ -213,11 +289,16 @@ export function ScheduleAppointment(props: AppointmentsProps) { groupSlotsByAvailability(slotsQuery.data.results).map( ({ availability, slots }) => (
-

{availability.name}

+

+ {availability.name} +

{slots.map((slot) => { const percentage = slot.allocated / availability.tokens_per_slot; + const isPastSlot = + isSameDay(selectedDate, new Date()) && + isBefore(slot.start_datetime, new Date()); return (
diff --git a/src/types/facility/facility.ts b/src/types/facility/facility.ts index bd0374c0bd4..017d0146429 100644 --- a/src/types/facility/facility.ts +++ b/src/types/facility/facility.ts @@ -5,6 +5,11 @@ export interface FacilityBareMinimum { name: string; } +export interface FacilityBareMinimum { + id: string; + name: string; +} + export interface BaseFacility { id: string; name: string;