Skip to content

Commit

Permalink
Appointments Flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacobjeevan committed Dec 15, 2024
1 parent e120e7c commit 9afcc59
Show file tree
Hide file tree
Showing 20 changed files with 1,393 additions and 427 deletions.
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"dependencies": {
"@fontsource/figtree": "^5.1.1",
"date-fns": "^3.6.0",
"@googlemaps/react-wrapper": "^1.1.42",
"@googlemaps/typescript-guards": "^2.0.3",
"@headlessui/react": "^2.2.0",
Expand Down
118 changes: 118 additions & 0 deletions src/CAREUI/interactive/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from "react";

import { cn } from "@/lib/utils";

import CareIcon from "@/CAREUI/icons/CareIcon";

import { getMonthStartAndEnd } from "@/Utils/utils";

interface Props {
className?: string;
month?: Date;
onMonthChange?: (month: Date) => void;
renderDay?: (date: Date) => React.ReactNode;
highlightToday?: boolean;
}

export default function Calendar(props: Props) {
const currentMonth = props.month ?? new Date();
const highlightToday = props.highlightToday ?? true;
const currentMonthRange = getMonthStartAndEnd(currentMonth);

// Calculate days to display from previous month
const startingDayOfWeek = currentMonthRange.start.getDay();

// Generate calendar days array for current month only
const calendarDays: Date[] = [];

// Add empty slots for previous month days
for (let i = 0; i < startingDayOfWeek; i++) {
calendarDays.push(null as unknown as Date);
}

// Add current month's days
for (let i = 1; i <= currentMonthRange.end.getDate(); i++) {
calendarDays.push(
new Date(currentMonth.getFullYear(), currentMonth.getMonth(), i),
);
}

const handlePrevMonth = () => {
const prevMonth = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() - 1,
);
props.onMonthChange?.(prevMonth);
};

const handleNextMonth = () => {
const nextMonth = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + 1,
);
props.onMonthChange?.(nextMonth);
};

const weekDays = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];

return (
<div className={`${props.className} w-full`}>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-bold uppercase">
{currentMonth.toLocaleString("default", {
month: "long",
year: "numeric",
})}
</h2>
<div className="flex gap-2">
<button
onClick={handlePrevMonth}
className="rounded-lg bg-gray-100 p-2 hover:bg-gray-200"
>
<CareIcon icon="l-angle-left" />
</button>
<button
onClick={handleNextMonth}
className="rounded-lg bg-gray-100 p-2 hover:bg-gray-200"
>
<CareIcon icon="l-angle-right" />
</button>
</div>
</div>

<div className="grid grid-cols-7 gap-0.5 md:gap-1.5">
{weekDays.map((day) => (
<div key={day} className="text-center font-medium">
{day}
</div>
))}

{calendarDays.map((date, index) => {
if (!date) {
return <div key={`empty-${index}`} className="min-h-[80px]" />;
}

const isToday = date.toDateString() === new Date().toDateString();

return (
<div
key={index}
className={cn(
"relative min-h-16 rounded-lg",
isToday && highlightToday && "ring-2 ring-primary-400",
)}
>
{props.renderDay ? (
props.renderDay(date)
) : (
<span className="block text-right p-2 transition-all rounded-lg bg-white text-gray-900">
{date.getDate()}
</span>
)}
</div>
);
})}
</div>
</div>
);
}
62 changes: 49 additions & 13 deletions src/Routers/SessionRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,69 @@ import ResetPassword from "@/components/Auth/ResetPassword";
import InvalidReset from "@/components/ErrorPages/InvalidReset";
import SessionExpired from "@/components/ErrorPages/SessionExpired";

import { DoctorAppointmentsPage } from "@/pages/Facility/DoctorAppointmentsPage";
import { AppointmentSuccess } from "@/pages/Appoinments/AppointmentSuccess";
import OTP from "@/pages/Appoinments/OTP";
import { AppointmentsPage } from "@/pages/Facility/AppointmentsPage";
import { FacilitiesPage } from "@/pages/Facility/FacilitiesPage";
import { FacilityDetailsPage } from "@/pages/Facility/FacilityDetailsPage";
import { LandingPage } from "@/pages/Landing/LandingPage";
import { PatientRegistration } from "@/pages/Patient/PatientRegistration";
import PatientSelect from "@/pages/Patient/PatientSelect";

const LicensesPage = lazy(() => import("@/components/Licenses/LicensesPage"));

const routes = {
"/": () => <LandingPage />,
"/facilities": () => <FacilitiesPage />,
"/facility/:id": ({ id }: { id: string }) => <FacilityDetailsPage id={id} />,
"/facility/:id/appointments/:doctorUsername": ({
id,
doctorUsername,
"/facility/:facilityId/appointments/:staffUsername/otp/:page": ({
facilityId,
staffUsername,
page,
}: {
id: string;
doctorUsername: string;
facilityId: string;
staffUsername: string;
page: string;
}) => (
<DoctorAppointmentsPage facilityId={id} doctorUsername={doctorUsername} />
<OTP facilityId={facilityId} staffUsername={staffUsername} page={page} />
),
"/facility/:id/appointments/:doctorUsername/patient-registration": ({
id,
doctorUsername,
"/facility/:facilityId/appointments/:staffUsername/book-appointment": ({
facilityId,
staffUsername,
}: {
id: string;
doctorUsername: string;
}) => <PatientRegistration facilityId={id} doctorUsername={doctorUsername} />,
facilityId: string;
staffUsername: string;
}) => (
<AppointmentsPage facilityId={facilityId} staffUsername={staffUsername} />
),
"/facility/:facilityId/appointments/:staffUsername/patient-select": ({
facilityId,
staffUsername,
}: {
facilityId: string;
staffUsername: string;
}) => <PatientSelect facilityId={facilityId} staffUsername={staffUsername} />,
"/facility/:facilityId/appointments/:staffUsername/patient-registration": ({
facilityId,
staffUsername,
}: {
facilityId: string;
staffUsername: string;
}) => (
<PatientRegistration
facilityId={facilityId}
staffUsername={staffUsername}
/>
),
"/facility/:facilityId/appointments/:appointmentId/success": ({
facilityId,
appointmentId,
}: {
facilityId: string;
appointmentId: string;
}) => (
<AppointmentSuccess facilityId={facilityId} appointmentId={appointmentId} />
),
"/login": () => <Login />,
"/forgot-password": () => <Login forgot={true} />,
"/password_reset/:token": ({ token }: { token: string }) => (
Expand All @@ -45,5 +80,6 @@ const routes = {
};

export default function SessionRouter() {
console.log("Current path:", window.location.pathname); // Add this logging
return useRoutes(routes) || <Login />;
}
36 changes: 36 additions & 0 deletions src/Utils/request/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,42 @@ const routes = {
method: "GET",
TRes: Type<PaginatedResponse<Observation>>(),
},

// OTP Routes
otp: {
sendOtp: {
path: "/api/v1/otp/send/",
method: "POST",
TBody: Type<{ phone_number: string }>(),
TRes: Type<Record<string, never>>(),
auth: {
key: "Authorization",
value: "{OTP_API_KEY}",
type: "header",
},
},
loginByOtp: {
path: "/api/v1/otp/login/",
method: "POST",
TBody: Type<{ phone_number: string; otp: string }>(),
TRes: Type<Record<string, never>>(),
auth: {
key: "Authorization",
value: "{OTP_API_KEY}",
type: "header",
},
},
getPatient: {
path: "/api/v1/otp/patient/",
method: "GET",
TRes: Type<PaginatedResponse<PatientModel>>(),
auth: {
key: "Authorization",
value: "Bearer {token}",
type: "header",
},
},
},
} as const;

export default routes;
7 changes: 7 additions & 0 deletions src/Utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,10 @@ export const properCase = (str: string) => {
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
};

export const getMonthStartAndEnd = (date: Date) => {
return {
start: new Date(date.getFullYear(), date.getMonth(), 1),
end: new Date(date.getFullYear(), date.getMonth() + 1, 0),
};
};
2 changes: 2 additions & 0 deletions src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Props<T extends FormDetails> = {
resetFormValsOnCancel?: boolean;
resetFormValsOnSubmit?: boolean;
hideCancelButton?: boolean;
submitButtonClassName?: string;
};

const Form = <T extends FormDetails>({
Expand Down Expand Up @@ -151,6 +152,7 @@ const Form = <T extends FormDetails>({
type="submit"
disabled={disabled}
label={props.submitLabel ?? "Submit"}
className={props?.submitButtonClassName}
/>
</div>
</Provider>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Form/FormFields/OtpFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const OtpFormField = ({ length = 6, ...props }: TextAreaFormFieldProps) => {

return (
<FormField field={props}>
<div className="flex items-center justify-evenly">
<div className="flex items-center justify-center">
{new Array(length).fill(null).map((_, i) => (
<input
key={i}
ref={(element) => (inputs.current[i] = element)}
className="form-control m-2 h-10 w-10 rounded border border-secondary-600 text-center"
className="form-control m-1 h-10 w-10 rounded border border-secondary-400 text-center shadow"
maxLength={1}
value={props.value[i]}
onChange={(e) => {
Expand Down
25 changes: 22 additions & 3 deletions src/components/Schedule/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {
Appointment,
AppointmentCreate,
ScheduleException,
ScheduleExceptionCreate,
ScheduleTemplate,
ScheduleTemplateCreate,
} from "@/components/Schedule/schemas";
TokenSlot,
} from "@/components/Schedule/types";
import { UserBareMinimum } from "@/components/Users/models";

import { Type } from "@/Utils/request/api";
Expand Down Expand Up @@ -49,10 +52,26 @@ export const ScheduleAPIs = {
method: "GET",
TRes: Type<PaginatedResponse<UserBareMinimum>>(),
},
availableSlots: {
slots: {
path: "/api/v1/facility/{facility_id}/appointments/slots/",
method: "GET",
TRes: Type<PaginatedResponse<unknown>>(),
TRes: Type<TokenSlot[]>(),
},
create: {
path: "/api/v1/facility/{facility_id}/appointments/",
method: "POST",
TBody: Type<AppointmentCreate>(),
TRes: Type<Appointment>(),
},
list: {
path: "/api/v1/facility/{facility_id}/appointments/",
method: "GET",
TRes: Type<PaginatedResponse<Appointment>>(),
},
retrieve: {
path: "/api/v1/facility/{facility_id}/appointments/{id}/",
method: "GET",
TRes: Type<Appointment>(),
},
},
} as const;
Loading

0 comments on commit 9afcc59

Please sign in to comment.