coronasafe/care_fe

View on GitHub
src/pages/Scheduling/components/EditScheduleTemplateSheet.tsx

Summary

Maintainability
F
1 wk
Test Coverage
File `EditScheduleTemplateSheet.tsx` has 922 lines of code (exceeds 250 allowed). Consider refactoring.
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { isBefore, parse } from "date-fns";
import { Loader2, SaveIcon, Trash2Icon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { toast } from "sonner";
import * as z from "zod";
 
import { cn } from "@/lib/utils";
 
import Callout from "@/CAREUI/display/Callout";
import CareIcon from "@/CAREUI/icons/CareIcon";
import WeekdayCheckbox, {
DayOfWeek,
} from "@/CAREUI/interactive/WeekdayCheckbox";
 
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button, buttonVariants } from "@/components/ui/button";
import { DatePicker } from "@/components/ui/date-picker";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
 
import { formatAvailabilityTime } from "@/components/Users/UserAvailabilityTab";
 
import mutate from "@/Utils/request/mutate";
import { Time } from "@/Utils/types";
import { dateQueryString } from "@/Utils/utils";
import {
calculateSlotDuration,
getSlotsPerSession,
getTokenDuration,
} from "@/pages/Scheduling/utils";
import {
AvailabilityDateTime,
ScheduleAvailability,
ScheduleAvailabilityCreateRequest,
ScheduleTemplate,
} from "@/types/scheduling/schedule";
import scheduleApis from "@/types/scheduling/scheduleApi";
 
export default function EditScheduleTemplateSheet({
template,
facilityId,
userId,
trigger,
open,
onOpenChange,
}: {
template: ScheduleTemplate;
facilityId: string;
userId: string;
trigger?: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}) {
const { t } = useTranslation();
 
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetTrigger asChild>
{trigger || <Button variant="outline" size="sm"></Button>}
</SheetTrigger>
<SheetContent className="flex min-w-full flex-col bg-gray-100 sm:min-w-[32rem]">
Similar blocks of code found in 2 locations. Consider refactoring.
<SheetHeader>
<SheetTitle>{t("edit_schedule_template")}</SheetTitle>
<SheetDescription className="sr-only">
{t("edit_schedule_template")}
</SheetDescription>
</SheetHeader>
<div className="overflow-auto -mx-6 px-6 pb-16">
<ScheduleTemplateEditor
template={template}
facilityId={facilityId}
userId={userId}
/>
 
<div className="mt-4">
<h2 className="text-lg font-semibold">{t("availabilities")}</h2>
</div>
 
{template.availabilities.length === 0 && (
<div className="mt-4">
<p className="text-sm text-gray-500">
{t("no_availabilities_yet")}
</p>
</div>
)}
 
{template.availabilities.map((availability) => (
<AvailabilityEditor
key={availability.id}
availability={availability}
scheduleId={template.id}
facilityId={facilityId}
userId={userId}
/>
))}
 
<NewAvailabilityCard
scheduleId={template.id}
facilityId={facilityId}
userId={userId}
/>
</div>
</SheetContent>
</Sheet>
);
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
const ScheduleTemplateEditor = ({
template,
facilityId,
userId,
}: {
template: ScheduleTemplate;
facilityId: string;
userId: string;
}) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
 
const templateFormSchema = z
.object({
name: z.string().min(1, t("field_required")),
valid_from: z.date({
required_error: t("field_required"),
}),
valid_to: z.date({
required_error: t("field_required"),
}),
})
.refine(
(data) => {
return isBefore(data.valid_from, data.valid_to);
},
{
message: t("from_date_must_be_before_to_date"),
path: ["valid_from"],
},
);
 
const form = useForm<z.infer<typeof templateFormSchema>>({
resolver: zodResolver(templateFormSchema),
defaultValues: {
name: template.name,
valid_from: new Date(template.valid_from),
valid_to: new Date(template.valid_to),
},
});
 
const { mutate: updateTemplate, isPending: isUpdating } = useMutation({
mutationFn: mutate(scheduleApis.templates.update, {
pathParams: {
facility_id: facilityId,
id: template.id,
},
}),
onSuccess: () => {
toast.success("Schedule template updated successfully");
queryClient.invalidateQueries({
queryKey: ["user-schedule-templates", { facilityId, userId }],
});
},
});
 
const { mutate: deleteTemplate, isPending: isDeleting } = useMutation({
mutationFn: mutate(scheduleApis.templates.delete, {
pathParams: {
facility_id: facilityId,
id: template.id,
},
}),
Similar blocks of code found in 2 locations. Consider refactoring.
onSuccess: () => {
toast.success(t("template_deleted"));
queryClient.invalidateQueries({
queryKey: ["user-schedule-templates", { facilityId, userId }],
});
},
});
 
const isProcessing = isUpdating || isDeleting;
 
function onSubmit(values: z.infer<typeof templateFormSchema>) {
updateTemplate({
name: values.name,
valid_from: dateQueryString(values.valid_from),
valid_to: dateQueryString(values.valid_to),
});
}
 
return (
<div className="rounded-lg bg-white p-4 shadow-sm">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
Similar blocks of code found in 2 locations. Consider refactoring.
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel aria-required>
{t("schedule_template_name")}
</FormLabel>
<FormControl>
<Input
placeholder={t("schedule_template_name_placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
Similar blocks of code found in 2 locations. Consider refactoring.
<div className="grid md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="valid_from"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel aria-required>{t("valid_from")}</FormLabel>
<DatePicker
date={field.value}
onChange={(date) => field.onChange(date)}
/>
<FormMessage />
</FormItem>
)}
/>
 
<FormField
control={form.control}
name="valid_to"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel aria-required>{t("valid_to")}</FormLabel>
<DatePicker
date={field.value}
onChange={(date) => field.onChange(date)}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
 
<div className="flex justify-end gap-2">
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={(open) => setIsDeleteDialogOpen(open)}
>
<AlertDialogTrigger asChild>
<Button
type="button"
variant="outline"
disabled={isProcessing}
size="sm"
>
<Trash2Icon />
{isDeleting ? t("deleting") : t("delete")}
</Button>
</AlertDialogTrigger>
Similar blocks of code found in 3 locations. Consider refactoring.
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("are_you_sure")}</AlertDialogTitle>
<AlertDialogDescription>
<Alert variant="destructive" className="mt-4">
<AlertTitle>{t("warning")}</AlertTitle>
<AlertDescription>
{t(
"this_will_permanently_remove_the_scheduled_template_and_cannot_be_undone",
)}
</AlertDescription>
</Alert>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
onClick={() => setIsDeleteDialogOpen(false)}
>
{t("cancel")}
</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={() => {
deleteTemplate();
setIsDeleteDialogOpen(false);
}}
>
{isDeleting ? (
<Loader2 className="size-4 animate-spin mr-2" />
) : (
t("confirm")
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Button
variant="primary"
type="submit"
disabled={isUpdating || !form.formState.isDirty}
size="sm"
>
<SaveIcon />
{isUpdating ? t("saving") : t("save")}
</Button>
</div>
</form>
</Form>
</div>
);
};
 
Function `AvailabilityEditor` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
const AvailabilityEditor = ({
availability,
scheduleId,
facilityId,
userId,
}: {
availability: ScheduleAvailability;
scheduleId: string;
facilityId: string;
userId: string;
}) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
 
const { mutate: deleteAvailability, isPending: isDeleting } = useMutation({
mutationFn: mutate(scheduleApis.templates.availabilities.delete, {
pathParams: {
facility_id: facilityId,
schedule_id: scheduleId,
id: availability.id,
},
}),
Similar blocks of code found in 2 locations. Consider refactoring.
onSuccess: () => {
toast.success(t("schedule_availability_deleted_successfully"));
queryClient.invalidateQueries({
queryKey: ["user-schedule-templates", { facilityId, userId }],
});
},
});
 
// Group availabilities by day of week
const availabilitiesByDay = availability.availability.reduce(
(acc, curr) => {
const day = curr.day_of_week;
if (!acc[day]) {
acc[day] = [];
}
acc[day].push(curr);
return acc;
},
{} as Record<DayOfWeek, AvailabilityDateTime[]>,
);
 
// Calculate slots and duration for appointment type
const { totalSlots, tokenDuration } = (() => {
if (availability.slot_type !== "appointment")
return { totalSlots: null, tokenDuration: null };
 
Similar blocks of code found in 2 locations. Consider refactoring.
const slots = Math.floor(
getSlotsPerSession(
availability.availability[0].start_time,
availability.availability[0].end_time,
availability.slot_size_in_minutes,
) ?? 0,
);
 
const duration = getTokenDuration(
availability.slot_size_in_minutes,
availability.tokens_per_slot,
);
 
return { totalSlots: slots, tokenDuration: duration };
})();
 
return (
<div className="mt-4 rounded-lg bg-white p-4 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<CareIcon icon="l-clock" className="text-lg text-blue-600" />
<span className="font-semibold">{availability.name}</span>
<span className="rounded-full bg-blue-50 px-2 py-0.5 text-xs font-medium text-blue-700">
{t(`SCHEDULE_AVAILABILITY_TYPE__${availability.slot_type}`)}
</span>
</div>
 
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={(open) => setIsDeleteDialogOpen(open)}
>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => setIsDeleteDialogOpen(true)}
disabled={isDeleting}
>
<CareIcon icon="l-trash" className="text-lg" />
</Button>
</AlertDialogTrigger>
Similar blocks of code found in 3 locations. Consider refactoring.
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("are_you_sure")}</AlertDialogTitle>
<AlertDialogDescription>
<Alert variant="destructive" className="mt-4">
<AlertTitle>{t("warning")}</AlertTitle>
<AlertDescription>
{t(
"this_will_permanently_remove_the_session_and_cannot_be_undone",
)}
</AlertDescription>
</Alert>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setIsDeleteDialogOpen(false)}>
{t("cancel")}
</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={() => {
deleteAvailability();
setIsDeleteDialogOpen(false);
}}
>
{isDeleting ? (
<Loader2 className="size-4 animate-spin mr-2" />
) : (
t("confirm")
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
 
<div className="space-y-4">
{availability.slot_type === "appointment" && (
<div className="grid md:grid-cols-2 gap-3">
<div className="flex flex-col rounded-md bg-gray-50 p-3">
<span className="text-sm font-medium text-gray-600">
{t("slot_configuration")}
</span>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-2xl font-semibold text-gray-900">
{availability.slot_size_in_minutes}
</span>
<span className="text-sm font-normal text-gray-500">min</span>
<span className="mx-1 text-gray-400">×</span>
<span className="text-2xl font-semibold text-gray-900">
{availability.tokens_per_slot}
</span>
<span className="text-sm font-normal text-gray-500">
patients
</span>
</div>
<span className="mt-1 text-sm text-gray-500">
≈ {tokenDuration?.toFixed(1).replace(".0", "")} min per patient
</span>
</div>
 
<div className="flex flex-col rounded-md bg-gray-50 p-3">
<span className="text-sm font-medium text-gray-600">
{t("session_capacity")}
</span>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-2xl font-semibold text-gray-900">
{totalSlots}
</span>
<span className="text-sm font-normal text-gray-500">slots</span>
<span className="mx-1 text-gray-400">×</span>
<span className="text-2xl font-semibold text-gray-900">
{availability.tokens_per_slot}
</span>
<span className="text-sm font-normal text-gray-500">
patients
</span>
</div>
<span className="mt-1 text-sm text-gray-500">
= {totalSlots ? totalSlots * availability.tokens_per_slot : 0}{" "}
total patients
</span>
</div>
</div>
)}
 
<div>
<span className="text-sm font-medium text-gray-500">
{t("remarks")}
</span>
<p className="mt-1 text-sm text-gray-600">
{availability.reason || t("no_remarks")}
</p>
</div>
 
<div>
<span className="text-sm font-medium text-gray-500">
{t("schedule")}
</span>
<div className="mt-2 space-y-1 pl-2">
{Object.entries(availabilitiesByDay).map(([day, times]) => (
<p key={day} className="flex items-center gap-2 text-sm">
<span className="font-medium w-24 text-gray-600">
{DayOfWeek[parseInt(day)].charAt(0) +
DayOfWeek[parseInt(day)].slice(1).toLowerCase()}
</span>
<span className="text-gray-500">
{times
.map((time) => formatAvailabilityTime([time]))
.join(", ")}
</span>
</p>
))}
</div>
</div>
</div>
</div>
);
};
 
Function `NewAvailabilityCard` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
const NewAvailabilityCard = ({
scheduleId,
facilityId,
userId,
}: {
scheduleId: string;
facilityId: string;
userId: string;
}) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isExpanded, setIsExpanded] = useState(false);
 
Similar blocks of code found in 2 locations. Consider refactoring.
const formSchema = z
.object({
name: z.string().min(1, t("field_required")),
slot_type: z.enum(["appointment", "open", "closed"]),
Similar blocks of code found in 8 locations. Consider refactoring.
start_time: z
.string()
.min(1, t("field_required")) as unknown as z.ZodType<Time>,
Similar blocks of code found in 8 locations. Consider refactoring.
end_time: z
.string()
.min(1, t("field_required")) as unknown as z.ZodType<Time>,
slot_size_in_minutes: z.number().nullable(),
tokens_per_slot: z.number().nullable(),
reason: z.string(),
Identical blocks of code found in 2 locations. Consider refactoring.
weekdays: z
.array(z.number() as unknown as z.ZodType<DayOfWeek>)
.min(1, t("schedule_weekdays_min_error")),
is_auto_fill: z.boolean().optional(),
num_of_slots: z.number().min(1, t("number_min_error", { min: 0 })),
})
.refine(
(data) => {
// Parse time strings into Date objects for comparison
const startTime = parse(data.start_time, "HH:mm", new Date());
const endTime = parse(data.end_time, "HH:mm", new Date());
 
return isBefore(startTime, endTime);
},
{
message: t("start_time_must_be_before_end_time"),
path: ["start_time"], // This will show the error on the start_time field
},
);
 
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
slot_type: "appointment",
start_time: undefined,
end_time: undefined,
slot_size_in_minutes: null,
tokens_per_slot: null,
reason: "",
weekdays: [],
is_auto_fill: false,
num_of_slots: 1,
},
});
 
const { mutate: createAvailability, isPending } = useMutation({
mutationFn: mutate(scheduleApis.templates.availabilities.create, {
pathParams: {
facility_id: facilityId,
schedule_id: scheduleId,
},
}),
onSuccess: () => {
toast.success(t("schedule_availability_created_successfully"));
queryClient.invalidateQueries({
queryKey: ["user-schedule-templates", { facilityId, userId }],
});
form.reset();
setIsExpanded(false);
},
});
 
const timeAllocationCallout = () => {
const startTime = form.watch("start_time");
const endTime = form.watch("end_time");
const slotSizeInMinutes = form.watch("slot_size_in_minutes");
const tokensPerSlot = form.watch("tokens_per_slot");
 
if (!startTime || !endTime || !slotSizeInMinutes || !tokensPerSlot) {
return null;
}
 
const slotsPerSession = getSlotsPerSession(
startTime,
endTime,
slotSizeInMinutes,
);
const tokenDuration = getTokenDuration(slotSizeInMinutes, tokensPerSlot);
 
if (!slotsPerSession || !tokenDuration) return null;
 
Similar blocks of code found in 2 locations. Consider refactoring.
return (
<Callout variant="alert" badge="Info">
<Trans
i18nKey="schedule_slots_allocation_callout"
values={{
slots: Math.floor(slotsPerSession),
token_duration: tokenDuration.toFixed(1).replace(".0", ""),
}}
/>
</Callout>
);
};
 
function onSubmit(values: z.infer<typeof formSchema>) {
const availability = {
name: values.name,
slot_type: values.slot_type,
reason: values.reason,
Similar blocks of code found in 2 locations. Consider refactoring.
availability: values.weekdays.map((day) => ({
day_of_week: day,
start_time: values.start_time,
end_time: values.end_time,
})),
...(values.slot_type === "appointment"
? {
slot_size_in_minutes: values.slot_size_in_minutes!,
tokens_per_slot: values.tokens_per_slot!,
}
: {
slot_size_in_minutes: null,
tokens_per_slot: null,
}),
} as ScheduleAvailabilityCreateRequest;
 
createAvailability(availability);
}
 
if (!isExpanded) {
return (
<div className="mt-4">
<Button
variant="outline_primary"
onClick={() => setIsExpanded(true)}
className="w-full"
>
<CareIcon icon="l-plus" className="text-lg" />
<span>{t("add_another_session")}</span>
</Button>
</div>
);
}
const updateSlotDuration = () => {
const isAutoFill = form.watch("is_auto_fill");
if (isAutoFill) {
const duration = calculateSlotDuration(
form.watch("start_time"),
form.watch("end_time"),
form.watch("num_of_slots"),
);
form.setValue("slot_size_in_minutes", duration);
}
};
 
return (
<div className="mt-4 rounded-lg bg-white p-4 shadow-sm">
<div className="mb-4 flex items-center justify-between">
Similar blocks of code found in 3 locations. Consider refactoring.
<div className="flex items-center gap-3">
<CareIcon icon="l-clock" className="text-lg text-blue-600" />
<span className="font-semibold">{t("new_session")}</span>
</div>
</div>
 
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel aria-required>{t("session_title")}</FormLabel>
<FormControl>
<Input
placeholder={t("session_title_placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
{/* <FormField
control={form.control}
name="slot_type"
render={({ field }) => (
<FormItem>
<FormLabel aria-required>{t("session_type")}</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue>
{t(`SCHEDULE_AVAILABILITY_TYPE__${field.value}`)}
</SelectValue>
</SelectTrigger>
</FormControl>
<SelectContent>
{["appointment", "open", "closed"].map((type) => (
<SelectItem key={type} value={type}>
<p>{t(`SCHEDULE_AVAILABILITY_TYPE__${type}`)}</p>
<p className="text-xs text-gray-500">
{t(`SCHEDULE_AVAILABILITY_TYPE_DESCRIPTION__${type}`)}
</p>
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/> */}
 
<div className="flex flex-wrap">
Similar blocks of code found in 2 locations. Consider refactoring.
<FormField
control={form.control}
name="start_time"
render={({ field }) => (
<FormItem className="flex flex-col w-full">
<FormLabel aria-required>{t("start_time")}</FormLabel>
<FormControl>
<Input
type="time"
{...field}
onChange={(e) => {
field.onChange(e);
updateSlotDuration();
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
Similar blocks of code found in 2 locations. Consider refactoring.
<FormField
control={form.control}
name="end_time"
render={({ field }) => (
<FormItem className="flex flex-col w-full mt-2">
<FormLabel aria-required>{t("end_time")}</FormLabel>
<FormControl>
<Input
type="time"
{...field}
onChange={(e) => {
field.onChange(e);
updateSlotDuration();
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
 
{form.watch("slot_type") === "appointment" && (
<>
<div className="flex flex-wrap mt-0 pt-2 gap-2">
<div className="w-full gap-x-2 grid grid-cols-[auto_1fr_auto] mb-2 bg-gray-50 p-3 rounded-lg">
<CareIcon icon="l-bolt" className="text-lg text-blue-600" />
<Label
htmlFor={"auto-fill"}
className="text-sm font-medium cursor-pointer col-start-2"
>
{t("auto_fill_slot_duration")}
</Label>
<Switch
className="col-start-3"
id={"auto-fill"}
checked={form.watch(`is_auto_fill`)}
onCheckedChange={(checked) => {
form.setValue(`is_auto_fill`, checked);
if (checked) {
updateSlotDuration();
}
}}
/>
{form.watch(`is_auto_fill`) && (
<div className="row-start-2 col-start-2 col-span-2">
<FormField
control={form.control}
name={`num_of_slots`}
render={({ field }) => (
<FormItem className="flex flex-col mt-2 space-y-0">
<Label className="text-sm font-light">
{t("number_of_slots")}
</Label>
<FormControl>
<Input
type="number"
min={1}
defaultValue={1}
{...field}
className="shadow-none"
onChange={(e) => {
field.onChange(e.target.valueAsNumber);
updateSlotDuration();
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</div>
</div>
<div className="flex items-center gap-4">
<FormField
control={form.control}
name="slot_size_in_minutes"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel aria-required>
{t("schedule_slot_size_label")}
</FormLabel>
<FormControl>
<Input
type="number"
min={0}
placeholder="e.g. 10"
{...field}
value={field.value ?? ""}
onChange={(e) =>
field.onChange(e.target.valueAsNumber)
}
disabled={form.watch("is_auto_fill")}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
<FormField
control={form.control}
name="tokens_per_slot"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel aria-required>
{t("patients_per_slot")}
</FormLabel>
Similar blocks of code found in 2 locations. Consider refactoring.
<FormControl>
<Input
type="number"
min={0}
placeholder="e.g. 1"
{...field}
value={field.value ?? ""}
onChange={(e) =>
field.onChange(e.target.valueAsNumber)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{timeAllocationCallout()}
</>
)}
 
<FormField
control={form.control}
name="weekdays"
render={({ field }) => (
<FormItem>
<FormLabel aria-required>{t("schedule_weekdays")}</FormLabel>
<FormControl>
<WeekdayCheckbox
value={field.value}
onChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
<FormField
control={form.control}
name="reason"
Similar blocks of code found in 2 locations. Consider refactoring.
render={({ field }) => (
<FormItem>
<FormLabel>{t("remarks")}</FormLabel>
<FormControl>
<Textarea
placeholder={t("remarks_placeholder")}
className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
 
<div className="flex justify-end gap-2">
<Button
variant="outline"
type="button"
onClick={() => setIsExpanded(false)}
disabled={isPending}
>
{t("cancel")}
</Button>
Similar blocks of code found in 3 locations. Consider refactoring.
<Button variant="primary" type="submit" disabled={isPending}>
{isPending ? t("creating") : t("create")}
</Button>
</div>
</form>
</Form>
</div>
);
};