Skip to content

Commit

Permalink
Refactor Scheduling and Appointments; support for delete schedule, ca…
Browse files Browse the repository at this point in the history
…ncel booking, improved date filter in appointments page, etc. (#9799)
  • Loading branch information
rithviknishad authored Jan 10, 2025
1 parent e0062a0 commit 86eba9b
Show file tree
Hide file tree
Showing 54 changed files with 2,904 additions and 1,339 deletions.
82 changes: 78 additions & 4 deletions public/locale/en.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/CAREUI/display/Callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Callout({
return (
<div
className={cn(
"flex h-min gap-2 rounded-md px-2 py-1.5 text-sm/tight",
"flex items-center h-min gap-2 rounded-md px-2 py-2 text-sm/tight",
{
primary: "bg-primary-100/50 text-primary-800",
secondary: "bg-gray-50 text-gray-700",
Expand All @@ -29,7 +29,7 @@ export default function Callout({
>
<div
className={cn(
"h-min rounded-full border bg-white px-2 py-0.5",
"h-min rounded-full border bg-white px-2",
{
primary: "border-primary-200",
secondary: "border-secondary-300",
Expand All @@ -41,7 +41,9 @@ export default function Callout({
>
<span className="font-medium">{props.badge}</span>
</div>
<span className="font-medium">{props.children}</span>
<div className="flex-1">
<span className="font-medium">{props.children}</span>
</div>
</div>
);
}
100 changes: 53 additions & 47 deletions src/CAREUI/interactive/WeekdayCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,77 @@
import { useTranslation } from "react-i18next";

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

import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";

// 0 is Monday, 6 is Sunday - Python's convention.
const DAYS_OF_WEEK = {
MONDAY: 0,
TUESDAY: 1,
WEDNESDAY: 2,
THURSDAY: 3,
FRIDAY: 4,
SATURDAY: 5,
SUNDAY: 6,
} as const;
export enum DayOfWeek {
MONDAY = 0,
TUESDAY = 1,
WEDNESDAY = 2,
THURSDAY = 3,
FRIDAY = 4,
SATURDAY = 5,
SUNDAY = 6,
}

export type DayOfWeekValue = (typeof DAYS_OF_WEEK)[keyof typeof DAYS_OF_WEEK];
const dayOfWeekKeys = [
"MONDAY",
"TUESDAY",
"WEDNESDAY",
"THURSDAY",
"FRIDAY",
"SATURDAY",
"SUNDAY",
] as const;

interface Props {
value?: DayOfWeekValue[];
onChange?: (value: DayOfWeekValue[]) => void;
value: DayOfWeek[] | null;
onChange: (value: DayOfWeek[] | null) => void;
format?: "alphabet" | "short" | "long";
}

export default function WeekdayCheckbox({ value = [], onChange }: Props) {
export default function WeekdayCheckbox({
value = [],
onChange,
format = "alphabet",
}: Props) {
const selectedDays = value ?? [];
const { t } = useTranslation();

const handleDayToggle = (day: DayOfWeekValue) => {
const handleDayToggle = (day: DayOfWeek) => {
if (!onChange) return;

if (value.includes(day)) {
onChange(value.filter((d) => d !== day));
if (selectedDays.includes(day)) {
onChange(selectedDays.filter((d) => d !== day));
} else {
onChange([...value, day]);
onChange([...selectedDays, day]);
}
};

return (
<ul className="flex justify-between">
{Object.values(DAYS_OF_WEEK).map((day) => {
const isChecked = value.includes(day);
<div className="flex gap-2 md:gap-4">
{dayOfWeekKeys.map((day) => {
const dow = DayOfWeek[day as keyof typeof DayOfWeek];
const isSelected = selectedDays.includes(dow);

return (
<li key={day}>
<div
className={cn(
"flex flex-col items-center justify-center gap-2 rounded-lg border px-8 py-6 transition-all duration-200 ease-in-out",
isChecked
? "border-primary-500 bg-white shadow"
: "border-gray-300",
)}
>
<Checkbox
id={`day_of_week_checkbox_${day}`}
checked={isChecked}
onCheckedChange={() => handleDayToggle(day)}
/>
<label
htmlFor={`day_of_week_checkbox_${day}`}
className="cursor-pointer text-xs font-semibold uppercase"
onClick={(e) => e.stopPropagation()}
>
{t(`DAYS_OF_WEEK_SHORT__${day}`)}
</label>
</div>
</li>
<Button
key={dow}
type="button"
variant={isSelected ? "outline_primary" : "outline"}
onClick={() => handleDayToggle(dow)}
size={format === "alphabet" ? "icon" : "default"}
aria-pressed={isSelected}
aria-checked={isSelected}
aria-label={t(`DAYS_OF_WEEK__${dow}`)}
>
{format === "alphabet"
? day[0]
: format === "short"
? t(`DAYS_OF_WEEK_SHORT__${dow}`)
: t(`DAYS_OF_WEEK__${dow}`)}
</Button>
);
})}
</ul>
</div>
);
}
2 changes: 1 addition & 1 deletion src/Routers/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { AppSidebar } from "@/components/ui/sidebar/app-sidebar";
import ErrorBoundary from "@/components/Common/ErrorBoundary";
import ErrorPage from "@/components/ErrorPages/DefaultErrorPage";
import SessionExpired from "@/components/ErrorPages/SessionExpired";
import ScheduleRoutes from "@/components/Schedule/routes";

import useAuthUser from "@/hooks/useAuthUser";
import { usePluginRoutes } from "@/hooks/useCareApps";
Expand All @@ -18,6 +17,7 @@ import ConsultationRoutes from "@/Routers/routes/ConsultationRoutes";
import FacilityRoutes from "@/Routers/routes/FacilityRoutes";
import PatientRoutes from "@/Routers/routes/PatientRoutes";
import ResourceRoutes from "@/Routers/routes/ResourceRoutes";
import ScheduleRoutes from "@/Routers/routes/ScheduleRoutes";
import UserRoutes from "@/Routers/routes/UserRoutes";
import { PermissionProvider } from "@/context/PermissionContext";
import { PlugConfigEdit } from "@/pages/Apps/PlugConfigEdit";
Expand Down
8 changes: 4 additions & 4 deletions src/Routers/PatientRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { patientTabs } from "@/components/Patient/PatientDetailsTab";
import { PatientHome } from "@/components/Patient/PatientHome";

import PatientUserProvider from "@/Providers/PatientUserProvider";
import { PatientRegistration } from "@/pages/Appoinments/PatientRegistration";
import PatientSelect from "@/pages/Appoinments/PatientSelect";
import { ScheduleAppointment } from "@/pages/Appoinments/Schedule";
import { AppointmentSuccess } from "@/pages/Appoinments/Success";
import { FacilitiesPage } from "@/pages/Facility/FacilitiesPage";
import PatientIndex from "@/pages/Patient/index";
import { PatientRegistration } from "@/pages/PublicAppointments/PatientRegistration";
import PatientSelect from "@/pages/PublicAppointments/PatientSelect";
import { ScheduleAppointment } from "@/pages/PublicAppointments/Schedule";
import { AppointmentSuccess } from "@/pages/PublicAppointments/Success";

import PublicRouter from "./PublicRouter";

Expand Down
2 changes: 1 addition & 1 deletion src/Routers/PublicRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import ResetPassword from "@/components/Auth/ResetPassword";
import InvalidReset from "@/components/ErrorPages/InvalidReset";
import SessionExpired from "@/components/ErrorPages/SessionExpired";

import PatientLogin from "@/pages/Appoinments/auth/PatientLogin";
import { FacilitiesPage } from "@/pages/Facility/FacilitiesPage";
import { FacilityDetailsPage } from "@/pages/Facility/FacilityDetailsPage";
import { LandingPage } from "@/pages/Landing/LandingPage";
import PatientLogin from "@/pages/PublicAppointments/auth/PatientLogin";

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Redirect } from "raviger";

import AppointmentCreatePage from "@/components/Schedule/Appointments/AppointmentCreatePage";
import AppointmentDetailsPage from "@/components/Schedule/Appointments/AppointmentDetailsPage";
import AppointmentsPage from "@/components/Schedule/Appointments/AppointmentsPage";

import useAuthUser from "@/hooks/useAuthUser";

import { AppRoutes } from "@/Routers/AppRouter";
import AppointmentDetail from "@/pages/Appointments/AppointmentDetail";
import AppointmentsPage from "@/pages/Appointments/AppointmentsPage";
import BookAppointment from "@/pages/Appointments/BookAppointment";

const HomeFacilityRedirect = ({ suffix }: { suffix: string }) => {
const authUser = useAuthUser();
Expand All @@ -25,16 +24,13 @@ const ScheduleRoutes: AppRoutes = {
"/facility/:facilityId/patient/:patientId/book-appointment": ({
facilityId,
patientId,
}) => <AppointmentCreatePage facilityId={facilityId} patientId={patientId} />,
}) => <BookAppointment facilityId={facilityId} patientId={patientId} />,

"/facility/:facilityId/patient/:patientId/appointments/:appointmentId": ({
facilityId,
appointmentId,
}) => (
<AppointmentDetailsPage
facilityId={facilityId}
appointmentId={appointmentId}
/>
<AppointmentDetail facilityId={facilityId} appointmentId={appointmentId} />
),
};

Expand Down
17 changes: 10 additions & 7 deletions src/Utils/request/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ function isNotFound(error: HTTPError) {

type PydanticError = {
type: string;
loc: string[];
loc?: string[];
msg: string;
input: unknown;
url: string;
input?: unknown;
url?: string;
};

function isPydanticError(errors: unknown): errors is PydanticError[] {
Expand All @@ -86,12 +86,15 @@ function isPydanticError(errors: unknown): errors is PydanticError[] {

function handlePydanticErrors(errors: PydanticError[]) {
errors.map(({ type, loc, msg }) => {
const title = type
if (!loc) {
toast.error(msg);
return;
}
type = type
.replace("_", " ")
.replace(/\b\w/g, (char) => char.toUpperCase());

toast.error(`${title}: '${loc.join(".")}'`, {
description: msg,
toast.error(msg, {
description: `${type}: '${loc.join(".")}'`,
duration: 8000,
});
});
Expand Down
4 changes: 4 additions & 0 deletions src/Utils/request/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const makeQueryParams = (query: QueryParams) => {
return qParams.toString();
};

/**
* TODO: consider replacing this with inferring the types from the route and using a generic
* to ensure that the path params are not missing.
*/
const ensurePathNotMissingReplacements = (path: string) => {
const missingParams = path.match(/\{.*\}/g);

Expand Down
2 changes: 1 addition & 1 deletion src/Utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ export type WritableOnly<T> = T extends object
type IfEquals<X, Y, A = X, B = never> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B;

export type Time = `${number}:${number}`;
export type Time = `${number}:${number}` | `${number}:${number}:${number}`;
2 changes: 1 addition & 1 deletion src/components/Facility/FacilityUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function FacilityUsers(props: { facilityId: number }) {
}

return (
<Page title={`${t("users")}`} hideBack={true} breadcrumbs={false}>
<Page title={t("users")} hideBack={true} breadcrumbs={false}>
<CountBlock
text={t("total_users")}
count={userListData.count}
Expand Down
13 changes: 1 addition & 12 deletions src/components/Form/FormFields/DateFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,7 @@ type Props = FormFieldBaseProps<Date> & {
};

/**
* A FormField to pick date.
*
* Example usage:
*
* ```jsx
* <DateFormField
* {...field("user_date_of_birth")}
* label="Date of birth"
* required
* disableFuture // equivalent to max={new Date()}
* />
* ```
* @deprecated use shadcn/ui's date-picker instead
*/
const DateFormField = (props: Props) => {
const field = useFormFieldPropsResolver(props);
Expand Down
3 changes: 3 additions & 0 deletions src/components/Form/FormFields/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export const FieldErrorText = (props: ErrorProps) => {
);
};

/**
* @deprecated use shadcn/ui's solution for form fields instead along with react-hook-form
*/
const FormField = ({
field,
...props
Expand Down
3 changes: 3 additions & 0 deletions src/components/Form/FormFields/RadioFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type Props<T, V = string> = FormFieldBaseProps<V | null> & {
layout?: "vertical" | "horizontal" | "grid" | "auto";
};

/**
* @deprecated use shadcn/ui's radio-group instead
*/
const RadioFormField = <T, V extends string>(props: Props<T, V>) => {
const field = useFormFieldPropsResolver(props);
return (
Expand Down
6 changes: 6 additions & 0 deletions src/components/Form/FormFields/SelectFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type SelectFormFieldProps<T, V = T> = FormFieldBaseProps<V> & {
inputClassName?: string;
};

/**
* @deprecated use shadcn/ui's select instead
*/
export const SelectFormField = <T, V>(props: SelectFormFieldProps<T, V>) => {
const field = useFormFieldPropsResolver<V>(props);
return (
Expand Down Expand Up @@ -58,6 +61,9 @@ type MultiSelectFormFieldProps<T, V = T> = FormFieldBaseProps<V[]> & {
optionDisabled?: OptionCallback<T, boolean>;
};

/**
* @deprecated
*/
export const MultiSelectFormField = <T, V>(
props: MultiSelectFormFieldProps<T, V>,
) => {
Expand Down
3 changes: 3 additions & 0 deletions src/components/Form/FormFields/TextAreaFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export type TextAreaFormFieldProps = FormFieldBaseProps<string> & {
onBlur?: (event: React.FocusEvent<HTMLTextAreaElement>) => void;
};

/**
* @deprecated use shadcn/ui's textarea instead
*/
const TextAreaFormField = forwardRef(
(
{ rows = 3, ...props }: TextAreaFormFieldProps,
Expand Down
3 changes: 3 additions & 0 deletions src/components/Form/FormFields/TextFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export type TextFormFieldProps = FormFieldBaseProps<string> &
clearable?: boolean | undefined;
};

/**
* @deprecated use shadcn/ui's Input instead
*/
const TextFormField = forwardRef((props: TextFormFieldProps, ref) => {
const field = useFormFieldPropsResolver(props);
const { leading, trailing } = props;
Expand Down
4 changes: 2 additions & 2 deletions src/components/Patient/PatientDetailsTab/Appointments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ import {

import { Avatar } from "@/components/Common/Avatar";
import { PatientProps } from "@/components/Patient/PatientDetailsTab";
import { ScheduleAPIs } from "@/components/Schedule/api";

import query from "@/Utils/request/query";
import { formatDateTime, formatName } from "@/Utils/utils";
import scheduleApis from "@/types/scheduling/scheduleApis";

export const Appointments = (props: PatientProps) => {
const { patientData, facilityId, id } = props;
const { t } = useTranslation();

const { data } = useQuery({
queryKey: ["patient-appointments", id],
queryFn: query(ScheduleAPIs.appointments.list, {
queryFn: query(scheduleApis.appointments.list, {
pathParams: { facility_id: facilityId },
queryParams: { patient: id, limit: 100 },
}),
Expand Down
Loading

0 comments on commit 86eba9b

Please sign in to comment.