Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

robustify UserCreateForm #9852

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 12 additions & 78 deletions public/locale/en.json

Large diffs are not rendered by default.

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

import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

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

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

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

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

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

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

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

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

return (
<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>
<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>
);
})}
</div>
</ul>
);
}
2 changes: 1 addition & 1 deletion src/Routers/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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 @@ -17,7 +18,6 @@ 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
17 changes: 7 additions & 10 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,15 +86,12 @@ function isPydanticError(errors: unknown): errors is PydanticError[] {

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

toast.error(`${title}: '${loc.join(".")}'`, {
description: msg,
duration: 8000,
});
});
Expand Down
4 changes: 0 additions & 4 deletions src/Utils/request/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ 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}` | `${number}:${number}:${number}`;
export type Time = `${number}:${number}`;
6 changes: 5 additions & 1 deletion src/components/Facility/FacilityCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,11 @@ export const FacilityCreate = (props: FacilityProps) => {
>
{t("cancel")}
</Button>
<Button variant="primary" type="submit" disabled={isLoading}>
<Button
variant="primary"
type="submit"
disabled={isLoading || !form.formState.isDirty}
>
rajku-dev marked this conversation as resolved.
Show resolved Hide resolved
{isLoading ? (
<Loading />
) : facilityId ? (
Expand Down
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: 12 additions & 1 deletion src/components/Form/FormFields/DateFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,18 @@ type Props = FormFieldBaseProps<Date> & {
};

/**
* @deprecated use shadcn/ui's date-picker instead
* 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()}
* />
* ```
*/
const DateFormField = (props: Props) => {
const field = useFormFieldPropsResolver(props);
Expand Down
3 changes: 0 additions & 3 deletions src/components/Form/FormFields/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ 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: 0 additions & 3 deletions src/components/Form/FormFields/RadioFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ 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: 0 additions & 6 deletions src/components/Form/FormFields/SelectFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ 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 @@ -61,9 +58,6 @@ 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: 0 additions & 3 deletions src/components/Form/FormFields/TextAreaFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ 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: 0 additions & 3 deletions src/components/Form/FormFields/TextFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ 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
Loading