From 9e84a397b5d01c63ae0f8ca295c37102b1529d21 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 15:49:57 +0530 Subject: [PATCH 1/9] serialize facility bare minimum in appoinmtnet --- src/types/facility/facility.ts | 5 +++++ src/types/scheduling/schedule.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/types/facility/facility.ts b/src/types/facility/facility.ts index 4a14964e34a..2168bed2763 100644 --- a/src/types/facility/facility.ts +++ b/src/types/facility/facility.ts @@ -1,5 +1,10 @@ import { Organization } from "../organization/organization"; +export interface FacilityBareMinimum { + id: string; + name: string; +} + export interface BaseFacility { id: string; name: string; diff --git a/src/types/scheduling/schedule.ts b/src/types/scheduling/schedule.ts index e97e1a1b2a8..2e7fc2036dc 100644 --- a/src/types/scheduling/schedule.ts +++ b/src/types/scheduling/schedule.ts @@ -2,6 +2,7 @@ import { DayOfWeek } from "@/CAREUI/interactive/WeekdayCheckbox"; import { Time } from "@/Utils/types"; import { AppointmentPatient } from "@/pages/Patient/Utils"; +import { FacilityBareMinimum } from "@/types/facility/facility"; import { UserBase } from "@/types/user/user"; export type ScheduleSlotType = "appointment" | "open" | "closed"; @@ -137,6 +138,7 @@ export interface Appointment { reason_for_visit: string; user: UserBase; booked_by: UserBase | null; // This is null if the appointment was booked by the patient itself. + facility: FacilityBareMinimum; } export interface AppointmentCreateRequest { From 4527502348b41bc701db547ddc7d06363c809ed2 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 16:32:19 +0530 Subject: [PATCH 2/9] reschedule flow --- public/locale/en.json | 3 + src/pages/Appointments/AppointmentDetail.tsx | 83 +++++- src/pages/Appointments/BookAppointment.tsx | 228 +---------------- .../components/AppointmentSlotPicker.tsx | 236 ++++++++++++++++++ src/types/scheduling/schedule.ts | 1 + src/types/scheduling/scheduleApis.ts | 6 + 6 files changed, 338 insertions(+), 219 deletions(-) create mode 100644 src/pages/Appointments/components/AppointmentSlotPicker.tsx diff --git a/public/locale/en.json b/public/locale/en.json index b0c3404f084..ca98856e24c 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -397,6 +397,7 @@ "appointment_created_success": "Appointment created successfully", "appointment_details": "Appointment Details", "appointment_not_found": "Appointment not found", + "appointment_rescheduled_successfully": "Appointment rescheduled successfully", "appointment_type": "Appointment Type", "appointments": "Appointments", "approve": "Approve", @@ -1719,6 +1720,8 @@ "required": "Required", "required_quantity": "Required Quantity", "reschedule": "Reschedule", + "reschedule_appointment": "Reschedule Appointment", + "rescheduling": "Rescheduling...", "resend_otp": "Resend OTP", "reset": "Reset", "reset_password": "Reset Password", diff --git a/src/pages/Appointments/AppointmentDetail.tsx b/src/pages/Appointments/AppointmentDetail.tsx index 93ba489a65d..a3da5f15471 100644 --- a/src/pages/Appointments/AppointmentDetail.tsx +++ b/src/pages/Appointments/AppointmentDetail.tsx @@ -14,6 +14,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { differenceInYears, format, isSameDay } from "date-fns"; import { BanIcon, PrinterIcon } from "lucide-react"; import { navigate } from "raviger"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -43,6 +44,13 @@ import { SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; @@ -63,11 +71,14 @@ import { } from "@/pages/Appointments/utils"; import { Appointment, + AppointmentCancelledStatuses, AppointmentStatuses, AppointmentUpdateRequest, } from "@/types/scheduling/schedule"; import scheduleApis from "@/types/scheduling/scheduleApis"; +import { AppointmentSlotPicker } from "./components/AppointmentSlotPicker"; + interface Props { facilityId: string; appointmentId: string; @@ -388,6 +399,8 @@ const AppointmentActions = ({ }: AppointmentActionsProps) => { const { t } = useTranslation(); const queryClient = useQueryClient(); + const [isRescheduleOpen, setIsRescheduleOpen] = useState(false); + const [selectedSlotId, setSelectedSlotId] = useState(); const currentStatus = appointment.status; const isToday = isSameDay(appointment.token_slot.start_datetime, new Date()); @@ -406,7 +419,28 @@ const AppointmentActions = ({ }, }); - if (["fulfilled", "cancelled", "entered_in_error"].includes(currentStatus)) { + const { mutate: rescheduleAppointment, isPending: isRescheduling } = + useMutation({ + mutationFn: mutate(scheduleApis.appointments.reschedule, { + pathParams: { + facility_id: facilityId, + id: appointment.id, + }, + }), + onSuccess: (newAppointment: Appointment) => { + queryClient.invalidateQueries({ + queryKey: ["appointment", appointment.id], + }); + toast.success(t("appointment_rescheduled_successfully")); + setIsRescheduleOpen(false); + setSelectedSlotId(undefined); + navigate( + `/facility/${facilityId}/patient/${appointment.patient.id}/appointments/${newAppointment.id}`, + ); + }, + }); + + if (["fulfilled", ...AppointmentCancelledStatuses].includes(currentStatus)) { return null; } @@ -437,6 +471,53 @@ const AppointmentActions = ({ {t("view_patient")} + + + + + + + + {t("reschedule_appointment")} + + +
+ + +
+ + +
+
+
+
+ {currentStatus === "booked" && ( <> - ); - } - - const { booked_slots, total_slots } = availability; - const bookedPercentage = booked_slots / total_slots; - const tokensLeft = total_slots - booked_slots; - const isFullyBooked = tokensLeft <= 0; - - return ( - - ); - }; - const handleSubmit = async () => { if (!resourceId) { toast.error("Please select a practitioner"); @@ -268,103 +151,12 @@ export default function BookAppointment(props: Props) { !resourceId && "opacity-50 pointer-events-none", )} > -
- { - setSelectedMonth(month); - setSelectedSlotId(undefined); - }} - renderDay={renderDay} - className="mb-6" - highlightToday={false} - /> -
- -
-
-

{t("available_time_slots")}

-
- -
- {slotsQuery.data == null && ( -
-

- {t("to_view_available_slots_select_resource_and_date")} -

-
- )} - {slotsQuery.data?.results.length === 0 && ( -
-

- {t("no_slots_available_for_this_date")} -

-
- )} - {!!slotsQuery.data?.results.length && - groupSlotsByAvailability(slotsQuery.data.results).map( - ({ availability, slots }) => ( -
-

- {availability.name} -

-
- {slots.map((slot) => { - const percentage = - slot.allocated / availability.tokens_per_slot; - - return ( - - ); - })} -
- -
- ), - )} -
-
-
+
diff --git a/src/pages/Appointments/components/AppointmentSlotPicker.tsx b/src/pages/Appointments/components/AppointmentSlotPicker.tsx new file mode 100644 index 00000000000..53915cc4b4f --- /dev/null +++ b/src/pages/Appointments/components/AppointmentSlotPicker.tsx @@ -0,0 +1,236 @@ +import { useQuery } from "@tanstack/react-query"; +import { format, isBefore, isSameDay, startOfToday } from "date-fns"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import Calendar from "@/CAREUI/interactive/Calendar"; + +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; + +import query from "@/Utils/request/query"; +import { dateQueryString } from "@/Utils/utils"; +import scheduleApis from "@/types/scheduling/scheduleApis"; + +import { groupSlotsByAvailability, useAvailabilityHeatmap } from "../utils"; + +interface AppointmentSlotPickerProps { + facilityId: string; + resourceId?: string; + onSlotSelect: (slotId: string | undefined) => void; + selectedSlotId?: string; +} + +export function AppointmentSlotPicker({ + facilityId, + resourceId, + onSlotSelect, + selectedSlotId, +}: AppointmentSlotPickerProps) { + const { t } = useTranslation(); + const [selectedMonth, setSelectedMonth] = useState(new Date()); + const [selectedDate, setSelectedDate] = useState(new Date()); + + const heatmapQuery = useAvailabilityHeatmap({ + facilityId, + userId: resourceId, + month: selectedMonth, + }); + + const slotsQuery = useQuery({ + queryKey: ["slots", facilityId, resourceId, dateQueryString(selectedDate)], + queryFn: query(scheduleApis.slots.getSlotsForDay, { + pathParams: { facility_id: facilityId }, + body: { + user: resourceId ?? "", + day: dateQueryString(selectedDate), + }, + }), + enabled: !!resourceId && !!selectedDate, + }); + + const renderDay = (date: Date) => { + const isSelected = isSameDay(date, selectedDate); + const isBeforeToday = isBefore(date, startOfToday()); + const availability = heatmapQuery.data?.[dateQueryString(date)]; + + if ( + heatmapQuery.isFetching || + !availability || + availability.total_slots === 0 || + isBeforeToday + ) { + return ( + + ); + } + + const { booked_slots, total_slots } = availability; + const bookedPercentage = booked_slots / total_slots; + const tokensLeft = total_slots - booked_slots; + const isFullyBooked = tokensLeft <= 0; + + return ( + + ); + }; + + return ( + <> +
+ { + setSelectedMonth(month); + onSlotSelect(undefined); + }} + renderDay={renderDay} + className="mb-6" + highlightToday={false} + /> +
+ +
+
+

{t("available_time_slots")}

+
+ +
+ {slotsQuery.data == null && ( +
+

+ {t("to_view_available_slots_select_resource_and_date")} +

+
+ )} + {slotsQuery.data?.results.length === 0 && ( +
+

+ {t("no_slots_available_for_this_date")} +

+
+ )} + {!!slotsQuery.data?.results.length && + groupSlotsByAvailability(slotsQuery.data.results).map( + ({ availability, slots }) => ( +
+

+ {availability.name} +

+
+ {slots.map((slot) => { + const percentage = + slot.allocated / availability.tokens_per_slot; + + return ( + + ); + })} +
+ +
+ ), + )} +
+
+
+ + ); +} diff --git a/src/types/scheduling/schedule.ts b/src/types/scheduling/schedule.ts index 2e7fc2036dc..de23dfc1989 100644 --- a/src/types/scheduling/schedule.ts +++ b/src/types/scheduling/schedule.ts @@ -114,6 +114,7 @@ export const AppointmentNonCancelledStatuses = [ export const AppointmentCancelledStatuses = [ "cancelled", "entered_in_error", + "rescheduled", ] as const; export const AppointmentStatuses = [ diff --git a/src/types/scheduling/scheduleApis.ts b/src/types/scheduling/scheduleApis.ts index e24c73ea2e0..bf768663c8d 100644 --- a/src/types/scheduling/scheduleApis.ts +++ b/src/types/scheduling/scheduleApis.ts @@ -143,6 +143,12 @@ export default { TBody: Type<{ reason: "cancelled" | "entered_in_error" }>(), TRes: Type(), }, + reschedule: { + path: "/api/v1/facility/{facility_id}/appointments/{id}/reschedule/", + method: HttpMethod.POST, + TBody: Type<{ new_slot: string }>(), + TRes: Type(), + }, /** * Lists schedulable users for a facility */ From 8c6072f1d06a6367150ccbfda407acc00ee6a1cc Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 20:38:09 +0530 Subject: [PATCH 3/9] update locale and badge --- public/locale/en.json | 1 + src/pages/Appointments/AppointmentDetail.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/public/locale/en.json b/public/locale/en.json index ca98856e24c..6cbacd62610 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1721,6 +1721,7 @@ "required_quantity": "Required Quantity", "reschedule": "Reschedule", "reschedule_appointment": "Reschedule Appointment", + "rescheduled": "Rescheduled", "rescheduling": "Rescheduling...", "resend_otp": "Resend OTP", "reset": "Reset", diff --git a/src/pages/Appointments/AppointmentDetail.tsx b/src/pages/Appointments/AppointmentDetail.tsx index a3da5f15471..d3266702937 100644 --- a/src/pages/Appointments/AppointmentDetail.tsx +++ b/src/pages/Appointments/AppointmentDetail.tsx @@ -241,6 +241,7 @@ const AppointmentDetails = ({ fulfilled: "primary", entered_in_error: "destructive", cancelled: "destructive", + rescheduled: "secondary", noshow: "destructive", } as Partial< Record From 699fefe4f1e4be8173daf350cb154332af45f5b1 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 23:18:59 +0530 Subject: [PATCH 4/9] add toast notifications post reschedule / cancel and fix flickr --- public/locale/en.json | 2 + src/pages/Appointments/AppointmentDetail.tsx | 7 +-- src/pages/Patient/index.tsx | 45 ++++++++++++-------- src/types/scheduling/schedule.ts | 7 +++ 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 6cbacd62610..7c982a00ce0 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -394,9 +394,11 @@ "app_settings": "App Settings", "apply": "Apply", "appointment_booking_success": "Your appointment has been successfully booked!", + "appointment_cancelled": "Appointment has been cancelled!", "appointment_created_success": "Appointment created successfully", "appointment_details": "Appointment Details", "appointment_not_found": "Appointment not found", + "appointment_rescheduled": "Appointment has been rescheduled!", "appointment_rescheduled_successfully": "Appointment rescheduled successfully", "appointment_type": "Appointment Type", "appointments": "Appointments", diff --git a/src/pages/Appointments/AppointmentDetail.tsx b/src/pages/Appointments/AppointmentDetail.tsx index d3266702937..7c47d17c2e3 100644 --- a/src/pages/Appointments/AppointmentDetail.tsx +++ b/src/pages/Appointments/AppointmentDetail.tsx @@ -71,9 +71,9 @@ import { } from "@/pages/Appointments/utils"; import { Appointment, - AppointmentCancelledStatuses, AppointmentStatuses, AppointmentUpdateRequest, + ApppointmentCompletedStatuses, } from "@/types/scheduling/schedule"; import scheduleApis from "@/types/scheduling/scheduleApis"; @@ -414,6 +414,7 @@ const AppointmentActions = ({ }, }), onSuccess: () => { + toast.success(t("appointment_cancelled")); queryClient.invalidateQueries({ queryKey: ["appointment", appointment.id], }); @@ -429,10 +430,10 @@ const AppointmentActions = ({ }, }), onSuccess: (newAppointment: Appointment) => { + toast.success(t("appointment_rescheduled")); queryClient.invalidateQueries({ queryKey: ["appointment", appointment.id], }); - toast.success(t("appointment_rescheduled_successfully")); setIsRescheduleOpen(false); setSelectedSlotId(undefined); navigate( @@ -441,7 +442,7 @@ const AppointmentActions = ({ }, }); - if (["fulfilled", ...AppointmentCancelledStatuses].includes(currentStatus)) { + if (ApppointmentCompletedStatuses.includes(currentStatus)) { return null; } diff --git a/src/pages/Patient/index.tsx b/src/pages/Patient/index.tsx index 4cbcbd4df1b..75a639e6424 100644 --- a/src/pages/Patient/index.tsx +++ b/src/pages/Patient/index.tsx @@ -3,6 +3,7 @@ import dayjs from "dayjs"; import { Link, navigate } from "raviger"; import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -26,7 +27,10 @@ import query from "@/Utils/request/query"; import { formatName, formatPatientAge } from "@/Utils/utils"; import { formatAppointmentSlotTime } from "@/pages/Appointments/utils"; import PublicAppointmentApi from "@/types/scheduling/PublicAppointmentApi"; -import { Appointment } from "@/types/scheduling/schedule"; +import { + Appointment, + ApppointmentCompletedStatuses, +} from "@/types/scheduling/schedule"; function PatientIndex() { const { t } = useTranslation(); @@ -62,6 +66,9 @@ function PatientIndex() { }, }), onSuccess: () => { + setSelectedAppointment(undefined); + setAppointmentDialogOpen(false); + toast.success(t("appointment_cancelled")); queryClient.invalidateQueries({ queryKey: ["appointment", tokenData?.phoneNumber], }); @@ -144,23 +151,25 @@ function PatientIndex() { {t(appointment.status)} - - - - + {!ApppointmentCompletedStatuses.includes(appointment.status) && ( + + + + + )} diff --git a/src/types/scheduling/schedule.ts b/src/types/scheduling/schedule.ts index de23dfc1989..19f6095ceb0 100644 --- a/src/types/scheduling/schedule.ts +++ b/src/types/scheduling/schedule.ts @@ -122,6 +122,13 @@ export const AppointmentStatuses = [ ...AppointmentCancelledStatuses, ] as const; +export const ApppointmentCompletedStatuses: AppointmentStatus[] = [ + "fulfilled", + "cancelled", + "entered_in_error", + "rescheduled", +]; + export type AppointmentNonCancelledStatus = (typeof AppointmentNonCancelledStatuses)[number]; From 3332a704905ad77a66a19555f67c5a90470e4eb6 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 23:41:13 +0530 Subject: [PATCH 5/9] disable past slots --- src/pages/Appointments/components/AppointmentSlotPicker.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/Appointments/components/AppointmentSlotPicker.tsx b/src/pages/Appointments/components/AppointmentSlotPicker.tsx index 53915cc4b4f..f2feb48d6ca 100644 --- a/src/pages/Appointments/components/AppointmentSlotPicker.tsx +++ b/src/pages/Appointments/components/AppointmentSlotPicker.tsx @@ -180,6 +180,9 @@ export function AppointmentSlotPicker({ {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/pages/PublicAppointments/Schedule.tsx b/src/pages/PublicAppointments/Schedule.tsx index 5afeb4f6907..a71498a44cf 100644 --- a/src/pages/PublicAppointments/Schedule.tsx +++ b/src/pages/PublicAppointments/Schedule.tsx @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { format } from "date-fns"; import { Link, navigate } from "raviger"; import { useEffect, useState } from "react"; @@ -22,26 +22,31 @@ import { FacilityModel } from "@/components/Facility/models"; 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 { 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 +59,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 +132,60 @@ export function ScheduleAppointment(props: AppointmentsProps) { } } + const { mutate: createAppointment } = 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, + }); + }, + onError: (error) => { + toast.error(error?.message || t("failed_to_create_appointment")); + }, + }); + + const { mutate: cancelAppointment, isSuccess: cancelledAppointment } = + useMutation({ + mutationFn: mutate(PublicAppointmentApi.cancelAppointment, { + headers: { + Authorization: `Bearer ${tokenData.token}`, + }, + }), + onSuccess: () => { + toast.success(t("appointment_cancelled")); + queryClient.invalidateQueries({ + queryKey: ["appointment", tokenData.phoneNumber], + }); + }, + }); + + const handleRescheduleAppointment = (appointment: Appointment) => { + cancelAppointment({ + appointment: appointment.id, + patient: appointment.patient.id, + }); + if (cancelledAppointment) { + createAppointment({ + reason_for_visit: reason, + patient: appointment.patient.id, + }); + } + }; + useEffect(() => { setSelectedSlot(undefined); }, [selectedDate]); @@ -178,7 +255,7 @@ export function ScheduleAppointment(props: AppointmentsProps) {
- {facilityResponse?.data?.name} + {facilityResponse?.name}
@@ -188,7 +265,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}`} @@ -284,18 +363,22 @@ export function ScheduleAppointment(props: AppointmentsProps) {
From 1bba87c30566d97d5e43fd275dc6e8b6c626b0bc Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan Date: Wed, 22 Jan 2025 10:40:52 +0530 Subject: [PATCH 7/9] minor fix --- src/pages/Appointments/AppointmentDetail.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Appointments/AppointmentDetail.tsx b/src/pages/Appointments/AppointmentDetail.tsx index cb9cbcccd12..61567f55cae 100644 --- a/src/pages/Appointments/AppointmentDetail.tsx +++ b/src/pages/Appointments/AppointmentDetail.tsx @@ -71,6 +71,7 @@ import { import { FacilityData } from "@/types/facility/facility"; import { Appointment, + AppointmentFinalStatuses, AppointmentStatuses, AppointmentUpdateRequest, } from "@/types/scheduling/schedule"; From 9de774dd6906acd087270277f5ebe51ee9634ae9 Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan Date: Wed, 22 Jan 2025 11:59:06 +0530 Subject: [PATCH 8/9] tweaking reschedule --- public/locale/en.json | 1 + src/pages/PublicAppointments/Schedule.tsx | 58 ++++++++++++----------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 74cbc59a01e..043051da7ad 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1767,6 +1767,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/pages/PublicAppointments/Schedule.tsx b/src/pages/PublicAppointments/Schedule.tsx index a71498a44cf..f2c67af226b 100644 --- a/src/pages/PublicAppointments/Schedule.tsx +++ b/src/pages/PublicAppointments/Schedule.tsx @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { format } from "date-fns"; -import { Link, navigate } from "raviger"; +import { format, isBefore, isSameDay } from "date-fns"; +import { navigate } from "raviger"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -19,6 +19,7 @@ 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"; @@ -41,6 +42,7 @@ interface AppointmentsProps { export function ScheduleAppointment(props: AppointmentsProps) { const { t } = useTranslation(); + const { goBack } = useAppHistory(); const { facilityId, staffId, appointmentId } = props; const [selectedMonth, setSelectedMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); @@ -158,32 +160,29 @@ export function ScheduleAppointment(props: AppointmentsProps) { }, }); - const { mutate: cancelAppointment, isSuccess: cancelledAppointment } = - useMutation({ - mutationFn: mutate(PublicAppointmentApi.cancelAppointment, { - headers: { - Authorization: `Bearer ${tokenData.token}`, - }, - }), - onSuccess: () => { - toast.success(t("appointment_cancelled")); - queryClient.invalidateQueries({ - queryKey: ["appointment", tokenData.phoneNumber], - }); + const { mutate: cancelAppointment } = 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, }); - if (cancelledAppointment) { - createAppointment({ - reason_for_visit: reason, - patient: appointment.patient.id, - }); - } }; useEffect(() => { @@ -216,12 +215,10 @@ export function ScheduleAppointment(props: AppointmentsProps) {
@@ -292,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 (