src/pages/Appointments/AppointmentDetail.tsx
File `AppointmentDetail.tsx` has 597 lines of code (exceeds 250 allowed). Consider refactoring.import { CalendarIcon, CheckCircledIcon, ClockIcon, DownloadIcon, DrawingPinIcon, EnterIcon, EyeNoneIcon, MobileIcon, PersonIcon, PlusCircledIcon,} from "@radix-ui/react-icons";import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";import { differenceInYears, format, isSameDay } from "date-fns";import { BanIcon, Loader2, PrinterIcon } from "lucide-react";import { navigate } from "raviger";import { useEffect, useState } from "react";import { useTranslation } from "react-i18next";import { formatPhoneNumberIntl } from "react-phone-number-input";import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger,} from "@/components/ui/alert-dialog";import { Badge, BadgeProps } from "@/components/ui/badge";import { Button, buttonVariants } from "@/components/ui/button";import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";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"; import useAppHistory from "@/hooks/useAppHistory"; import { getPermissions } from "@/common/Permissions"; import routes from "@/Utils/request/api";import mutate from "@/Utils/request/mutate";import query from "@/Utils/request/query";import { formatName, getReadableDuration, saveElementAsImage, stringifyNestedObject,} from "@/Utils/utils";import { usePermissions } from "@/context/PermissionContext";import { AppointmentTokenCard } from "@/pages/Appointments/components/AppointmentTokenCard";import { FacilityData } from "@/types/facility/facility";import { Appointment, AppointmentFinalStatuses, AppointmentUpdateRequest,} from "@/types/scheduling/schedule";import scheduleApis from "@/types/scheduling/scheduleApi"; import { AppointmentSlotPicker } from "./components/AppointmentSlotPicker"; interface Props { facilityId: string; appointmentId: string;} Function `AppointmentDetail` has 123 lines of code (exceeds 25 allowed). Consider refactoring.
Function `AppointmentDetail` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.export default function AppointmentDetail(props: Props) { const { t } = useTranslation(); const queryClient = useQueryClient(); const { hasPermission } = usePermissions(); const { goBack } = useAppHistory(); Similar blocks of code found in 2 locations. Consider refactoring. const { data: facilityData, isLoading: isFacilityLoading } = useQuery({ queryKey: ["facility", props.facilityId], queryFn: query(routes.getPermittedFacility, { pathParams: { id: props.facilityId, }, }), }); const { canViewAppointments, canUpdateAppointment, canCreateAppointment } = getPermissions(hasPermission, facilityData?.permissions ?? []); const { data: appointment } = useQuery({ queryKey: ["appointment", props.appointmentId], queryFn: query(scheduleApis.appointments.retrieve, { pathParams: { facility_id: props.facilityId, id: props.appointmentId, }, }), enabled: canViewAppointments, }); const redirectToPatientPage = () => { navigate(`/facility/${props.facilityId}/patients/verify`, { query: { phone_number: patient.phone_number, year_of_birth: patient.year_of_birth, partial_id: patient.id.slice(0, 5), }, }); }; useEffect(() => { if (!canViewAppointments && !isFacilityLoading) { toast.error(t("no_permission_to_view_page")); goBack(`/facility/${props.facilityId}/overview`); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [canViewAppointments, isFacilityLoading]); const { mutate: updateAppointment, isPending } = useMutation< Appointment, unknown, AppointmentUpdateRequest >({ mutationFn: mutate(scheduleApis.appointments.update, { pathParams: { facility_id: props.facilityId, id: props.appointmentId, }, }), onSuccess: (_, request) => { queryClient.invalidateQueries({ queryKey: ["appointment", props.appointmentId], }); if (request.status === "in_consultation") { redirectToPatientPage(); } }, }); if (!facilityData || !appointment) { return <Loading />; } const { patient } = appointment; return ( <Page title={t("appointment_details")}> <div className="container mx-auto p-6 max-w-7xl"> <div className={cn( "flex flex-col md:flex-col lg:flex-row", isPending && "opacity-50 pointer-events-none animate-pulse", )} > <AppointmentDetails appointment={appointment} facility={facilityData} /> <div className="mt-3"> <div id="section-to-print" className="print:w-[400px] print:pt-4"> <div id="appointment-token-card" className="bg-gray-50 md:p-4"> <AppointmentTokenCard appointment={appointment} facility={facilityData} /> </div> </div> <div className="flex gap-2 justify-end px-6 mt-4 md:mt-0"> <Button variant="outline" onClick={() => print()}> <PrinterIcon className="size-4 mr-2" /> <span>{t("print")}</span> </Button> <Button variant="default" onClick={async () => { await saveElementAsImage( "appointment-token-card", `${patient.name}'s Appointment.png`, ); toast.success("Appointment card has been saved!"); }} > <DownloadIcon className="size-4 mr-2" /> <span>{t("save")}</span> </Button> </div> {canUpdateAppointment && ( <> <Separator className="my-4" /> <div className="md:mx-6 mt-10"> <AppointmentActions facilityId={props.facilityId} appointment={appointment} onChange={(status) => updateAppointment({ status })} onViewPatient={redirectToPatientPage} canCreateAppointment={canCreateAppointment} /> </div> </> )} </div> </div> </div> </Page> );} const AppointmentDetails = ({ appointment, facility,}: { appointment: Appointment; facility: FacilityData;}) => { const { user } = appointment; const { t } = useTranslation(); return ( <div className="container md:p-6 max-w-3xl space-y-6"> <Card> <CardHeader> <CardTitle> <span className="mr-3 inline-block mb-2"> {t("schedule_information")} </span> <Badge variant={ ( { booked: "secondary", checked_in: "primary", in_consultation: "primary", pending: "secondary", arrived: "primary", fulfilled: "primary", entered_in_error: "destructive", cancelled: "destructive", rescheduled: "secondary", noshow: "destructive", } as Partial< Record<Appointment["status"], BadgeProps["variant"]> > )[appointment.status] ?? "outline" } > {t(appointment.status)} </Badge> </CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="flex items-center space-x-4 text-sm"> <CalendarIcon className="size-5 text-gray-600" /> <div> <p className="font-medium"> {format(appointment.token_slot.start_datetime, "MMMM d, yyyy")} </p> <p className="text-gray-600"> {appointment.token_slot.availability.name} </p> </div> </div> <div className="flex items-center space-x-4 text-sm"> <ClockIcon className="size-5 text-gray-600" /> <div> <p className="font-medium"> {format(appointment.token_slot.start_datetime, "h:mm a")} -{" "} {format(appointment.token_slot.end_datetime, "h:mm a")} </p> <p className="text-gray-600 capitalize"> {t("duration")}:{" "} {getReadableDuration( appointment.token_slot.start_datetime, appointment.token_slot.end_datetime, )} </p> </div> </div> <Separator /> <div className="text-sm"> <p className="font-medium">{t("reason_for_visit")}</p> <p className="text-gray-600"> {appointment.reason_for_visit || t("no_reason_provided")} </p> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle>{t("patient_information")}</CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="flex items-center space-x-4 text-sm"> <PersonIcon className="size-5 text-gray-600" /> <div> <p className="font-medium">{appointment.patient.name}</p> <p className="text-gray-600"> {appointment.patient.date_of_birth ? ( <> {format(appointment.patient.date_of_birth, "MMMM d, yyyy")}{" "} |{" "} {differenceInYears( new Date(), appointment.patient.date_of_birth!, )} </> ) : ( <> {differenceInYears( new Date(), new Date().setFullYear( Number(appointment.patient.year_of_birth), ), )} </> )}{" "} {t("years")} </p> </div> </div> <div className="flex items-center space-x-4 text-sm"> <MobileIcon className="size-5 text-gray-600" /> <div> <p className="font-medium">Similar blocks of code found in 2 locations. Consider refactoring. <a href={`tel:${appointment.patient.phone_number}`} className="text-primary hover:underline" > {formatPhoneNumberIntl(appointment.patient.phone_number)} </a> </p> <p className="text-gray-600"> {t("emergency")}:{" "} {appointment.patient.emergency_phone_number && (Similar blocks of code found in 2 locations. Consider refactoring. <a href={`tel:${appointment.patient.emergency_phone_number}`} className="text-primary hover:underline" > {formatPhoneNumberIntl( appointment.patient.emergency_phone_number, )} </a> )} </p> </div> </div> <div className="flex items-center space-x-4 text-sm"> <DrawingPinIcon className="size-5 text-gray-600" /> <div> <p className="font-medium"> {appointment.patient.address || t("no_address_provided")} </p> <p className="text-gray-600"> {stringifyNestedObject(appointment.patient.geo_organization)} </p> <p className="text-gray-600"> {t("pincode")}: {appointment.patient.pincode} </p> </div> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle>{t("practitioner_information")}</CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="grid gap-2"> <div className="text-sm"> <p className="font-medium">{formatName(user)}</p> <p className="text-gray-600">{user.email}</p> </div> <Separator />Similar blocks of code found in 3 locations. Consider refactoring. <div className="text-sm"> <p className="font-medium">{t("facility")}</p> <p className="text-gray-600">{facility.name}</p> </div> </div> </CardContent> </Card> <div className="text-sm text-gray-600"> {t("booked_by")} {appointment.booked_by?.first_name}{" "} {appointment.booked_by?.last_name} {t("on")}{" "} {format(appointment.booked_on, "MMMM d, yyyy 'at' h:mm a")} </div> </div> );}; interface AppointmentActionsProps { facilityId: string; appointment: Appointment; onChange: (status: Appointment["status"]) => void; onViewPatient: () => void; canCreateAppointment: boolean;} Function `AppointmentActions` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.const AppointmentActions = ({ facilityId, appointment, onChange, onViewPatient, canCreateAppointment,}: AppointmentActionsProps) => { const { t } = useTranslation(); const queryClient = useQueryClient(); const [isRescheduleOpen, setIsRescheduleOpen] = useState(false); const [selectedSlotId, setSelectedSlotId] = useState<string>(); const currentStatus = appointment.status; const isToday = isSameDay(appointment.token_slot.start_datetime, new Date()); const { mutate: cancelAppointment, isPending: isCancelling } = useMutation({ mutationFn: mutate(scheduleApis.appointments.cancel, { pathParams: { facility_id: facilityId, id: appointment.id, }, }),Similar blocks of code found in 2 locations. Consider refactoring. onSuccess: () => { toast.success(t("appointment_cancelled")); queryClient.invalidateQueries({ queryKey: ["appointment", appointment.id], }); }, }); const { mutate: rescheduleAppointment, isPending: isRescheduling } = useMutation({ mutationFn: mutate(scheduleApis.appointments.reschedule, { pathParams: { facility_id: facilityId, id: appointment.id, }, }), onSuccess: (newAppointment: Appointment) => { toast.success(t("appointment_rescheduled")); queryClient.invalidateQueries({ queryKey: ["appointment", appointment.id], }); setIsRescheduleOpen(false); setSelectedSlotId(undefined); navigate( `/facility/${facilityId}/patient/${appointment.patient.id}/appointments/${newAppointment.id}`, ); }, }); if (AppointmentFinalStatuses.includes(currentStatus)) { return null; } return ( <div className="flex flex-col gap-2 w-full md:w-64 mx-auto"> <Button variant="outline" onClick={onViewPatient} size="lg"> <PersonIcon className="size-4 mr-2" /> {t("view_patient")} </Button> {canCreateAppointment && ( <Sheet open={isRescheduleOpen} onOpenChange={setIsRescheduleOpen}> <SheetTrigger asChild> <Button variant="outline" size="lg"> <CalendarIcon className="size-4 mr-2" /> {t("reschedule")} </Button> </SheetTrigger> <SheetContent className="w-full sm:max-w-xl overflow-y-auto"> <SheetHeader> <SheetTitle>{t("reschedule_appointment")}</SheetTitle> </SheetHeader> <div className="mt-6"> <AppointmentSlotPicker facilityId={facilityId} resourceId={appointment.user?.id} selectedSlotId={selectedSlotId} onSlotSelect={setSelectedSlotId} /> <div className="flex justify-end gap-2 mt-6"> <Button variant="outline" onClick={() => { setIsRescheduleOpen(false); setSelectedSlotId(undefined); }} > {t("cancel")} </Button> <Button variant="default" disabled={!selectedSlotId || isRescheduling} onClick={() => { if (selectedSlotId) { rescheduleAppointment({ new_slot: selectedSlotId }); } }} > {isRescheduling ? t("rescheduling") : t("reschedule")} </Button> </div> </div> </SheetContent> </Sheet> )} {currentStatus === "booked" && ( <>Similar blocks of code found in 2 locations. Consider refactoring. <Button disabled={!isToday} variant="outline_primary" onClick={() => onChange("checked_in")} size="lg" > <EnterIcon className="size-4 mr-2" /> {t("check_in")} </Button> </> )} {["booked", "checked_in"].includes(currentStatus) && ( <Button disabled={!isToday} variant={ currentStatus === "checked_in" ? "outline_primary" : "outline" } onClick={() => onChange("in_consultation")} size="lg" > <PlusCircledIcon className="size-4 mr-2" /> {t("start_consultation")} </Button> )} {currentStatus === "in_consultation" && (Similar blocks of code found in 2 locations. Consider refactoring. <Button disabled={!isToday} variant="outline_primary" onClick={() => onChange("fulfilled")} size="lg" > <CheckCircledIcon className="size-4 mr-2" /> {t("mark_as_fulfilled")} </Button> )} {["booked", "checked_in"].includes(currentStatus) && ( <Button variant="outline" onClick={() => onChange("noshow")} size="lg"> <EyeNoneIcon className="size-4 mr-2" /> {t("mark_as_noshow")} </Button> )} Similar blocks of code found in 2 locations. Consider refactoring. <AlertDialog> <AlertDialogTrigger asChild> <Button variant="outline" size="lg"> <BanIcon className="size-4 mr-2" /> {t("cancel_appointment")} </Button> </AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>{t("cancel_appointment")}</AlertDialogTitle> <AlertDialogDescription> <Alert variant="destructive" className="mt-4"> <AlertTitle>{t("warning")}</AlertTitle> <AlertDescription> {t("cancel_appointment_warning")} </AlertDescription> </Alert> </AlertDialogDescription> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel>{t("cancel")}</AlertDialogCancel> <AlertDialogAction onClick={() => cancelAppointment({ reason: "cancelled" })} className={cn(buttonVariants({ variant: "destructive" }))} > {isCancelling ? ( <Loader2 className="size-4 animate-spin mr-2" /> ) : ( t("confirm") )} </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> Similar blocks of code found in 2 locations. Consider refactoring. <AlertDialog> <AlertDialogTrigger asChild> <Button variant="outline" size="lg"> <BanIcon className="size-4 mr-2" /> {t("mark_as_entered_in_error")} </Button> </AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>{t("mark_as_entered_in_error")}</AlertDialogTitle> <AlertDialogDescription> <Alert variant="destructive" className="mt-4"> <AlertTitle>{t("warning")}</AlertTitle> <AlertDescription> {t("entered_in_error_warning")} </AlertDescription> </Alert> </AlertDialogDescription> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel>{t("cancel")}</AlertDialogCancel> <AlertDialogAction onClick={() => cancelAppointment({ reason: "entered_in_error" })} className={cn(buttonVariants({ variant: "destructive" }))} > {isCancelling ? ( <Loader2 className="size-4 animate-spin mr-2" /> ) : ( t("confirm") )} </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </div> );};