diff --git a/public/locale/en.json b/public/locale/en.json
index 654cfa019f3..e060c325ab8 100644
--- a/public/locale/en.json
+++ b/public/locale/en.json
@@ -437,6 +437,7 @@
"authorize_shift_delete": "Authorize shift delete",
"auto_generated_for_care": "Auto Generated for Care",
"autofilled_fields": "Autofilled Fields",
+ "availabilities": "Availabilities",
"available_features": "Available Features",
"available_in": "Available in",
"available_time_slots": "Available Time Slots",
@@ -657,6 +658,7 @@
"created_by": "Created By",
"created_date": "Created Date",
"created_on": "Created On",
+ "creating": "Creating...",
"criticality": "Criticality",
"csv_file_in_the_specified_format": "Select a CSV file in the specified format",
"current_address": "Current Address",
@@ -1240,11 +1242,13 @@
"new_password_confirmation": "Confirm New Password",
"new_password_same_as_old": "New password is same as old password, please enter a different new password.",
"new_password_validation": "New password is not valid.",
+ "new_session": "New Session",
"next_sessions": "Next Sessions",
"no": "No",
"no_address_provided": "No address provided",
"no_appointments": "No appointments found",
"no_attachments_found": "This communication has no attachments.",
+ "no_availabilities_yet": "No availabilities yet",
"no_bed_asset_linked_allocated": "No bed/asset linked allocated",
"no_bed_types_found": "No Bed Types found",
"no_beds_available": "No beds available",
@@ -1624,8 +1628,8 @@
"scan_asset_qr": "Scan Asset QR!",
"schedule": "Schedule",
"schedule_appointment": "Schedule Appointment",
+ "schedule_availability_created_successfully": "Availability created successfully",
"schedule_availability_deleted_successfully": "Schedule availability deleted successfully",
- "schedule_availability_updated_successfully": "Schedule availability updated successfully",
"schedule_calendar": "Schedule Calendar",
"schedule_end_time": "End Time",
"schedule_for": "Scheduled for",
@@ -1633,7 +1637,6 @@
"schedule_remarks": "Remarks",
"schedule_remarks_placeholder": "Any additional notes about this session",
"schedule_session_time": "Session Time",
- "schedule_session_title": "Session Title",
"schedule_session_type": "Session Type",
"schedule_sessions": "Sessions",
"schedule_sessions_min_error": "Add at least one session",
@@ -1644,7 +1647,6 @@
"schedule_template": "Schedule Template",
"schedule_template_name": "Template Name",
"schedule_template_name_placeholder": "Regular OP Day",
- "schedule_tokens_per_slot": "Patients per Slot",
"schedule_valid_from_till_range": "Valid from {{from_date}} till {{to_date}}",
"schedule_weekdays": "Weekdays",
"schedule_weekdays_description": "Select the weekdays applicable for the template",
@@ -1704,6 +1706,7 @@
"send_sample_to_collection_centre_title": "Send sample to collection centre",
"serial_number": "Serial Number",
"serviced_on": "Serviced on",
+ "session_capacity": "Session Capacity",
"session_expired": "Session Expired",
"session_expired_msg": "It appears that your session has expired. This could be due to inactivity. Please login again to continue.",
"session_title": "Session Title",
@@ -1738,6 +1741,7 @@
"skill_add_error": "Error while adding skill",
"skill_added_successfully": "Skill added successfully",
"skills": "Skills",
+ "slot_configuration": "Slot Configuration",
"slots_left": "slots left",
"social_profile": "Social Profile",
"social_profile_detail": "Include occupation, ration card category, socioeconomic status, and domestic healthcare support for a complete profile.",
@@ -1803,6 +1807,7 @@
"total_amount": "Total Amount",
"total_beds": "Total Beds",
"total_patients": "Total Patients",
+ "total_slots": "Total Slots",
"total_staff": "Total Staff",
"total_users": "Total Users",
"transcribe_again": "Transcribe Again",
diff --git a/src/CAREUI/interactive/WeekdayCheckbox.tsx b/src/CAREUI/interactive/WeekdayCheckbox.tsx
index 1660d14ef02..55ebc304516 100644
--- a/src/CAREUI/interactive/WeekdayCheckbox.tsx
+++ b/src/CAREUI/interactive/WeekdayCheckbox.tsx
@@ -1,31 +1,32 @@
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 type DayOfWeekValue = (typeof DAYS_OF_WEEK)[keyof typeof DAYS_OF_WEEK];
+export enum DayOfWeek {
+ MONDAY = 0,
+ TUESDAY = 1,
+ WEDNESDAY = 2,
+ THURSDAY = 3,
+ FRIDAY = 4,
+ SATURDAY = 5,
+ SUNDAY = 6,
+}
interface Props {
- value?: DayOfWeekValue[];
- onChange?: (value: DayOfWeekValue[]) => void;
+ value?: DayOfWeek[];
+ onChange?: (value: DayOfWeek[]) => void;
+ format?: "alphabet" | "short" | "long";
}
-export default function WeekdayCheckbox({ value = [], onChange }: Props) {
+export default function WeekdayCheckbox({
+ value = [],
+ onChange,
+ format = "alphabet",
+}: Props) {
const { t } = useTranslation();
- const handleDayToggle = (day: DayOfWeekValue) => {
+ const handleDayToggle = (day: DayOfWeek) => {
if (!onChange) return;
if (value.includes(day)) {
@@ -36,36 +37,35 @@ export default function WeekdayCheckbox({ value = [], onChange }: Props) {
};
return (
-
- {Object.values(DAYS_OF_WEEK).map((day) => {
- const isChecked = value.includes(day);
+
+ {[
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+ "SUNDAY",
+ ].map((day) => {
+ const dow = DayOfWeek[day as keyof typeof DayOfWeek];
+ const isSelected = value.includes(dow);
return (
-
-
-
- handleDayToggle(day)}
- />
-
-
-
+
);
})}
-
+
);
}
diff --git a/src/components/Schedule/ScheduleTemplateEditForm.tsx b/src/components/Schedule/ScheduleTemplateEditForm.tsx
index 081255c7294..a2e952fe3cc 100644
--- a/src/components/Schedule/ScheduleTemplateEditForm.tsx
+++ b/src/components/Schedule/ScheduleTemplateEditForm.tsx
@@ -1,20 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { ArrowRightIcon } 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 Callout from "@/CAREUI/display/Callout";
import CareIcon from "@/CAREUI/icons/CareIcon";
+import WeekdayCheckbox, {
+ DayOfWeek,
+} from "@/CAREUI/interactive/WeekdayCheckbox";
import { Button } from "@/components/ui/button";
import { DatePicker } from "@/components/ui/date-picker";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
import {
Form,
FormControl,
@@ -24,20 +25,30 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
-import { getSlotsPerSession } from "@/components/Schedule/helpers";
+import {
+ getSlotsPerSession,
+ getTokenDuration,
+} from "@/components/Schedule/helpers";
import { formatAvailabilityTime } from "@/components/Users/UserAvailabilityTab";
import mutate from "@/Utils/request/mutate";
+import { Time } from "@/Utils/types";
import { dateQueryString } from "@/Utils/utils";
import {
AvailabilityDateTime,
ScheduleAvailability,
- ScheduleAvailabilityUpdateRequest,
+ ScheduleAvailabilityCreateRequest,
ScheduleTemplate,
} from "@/types/scheduling/schedule";
-import { DayOfWeek } from "@/types/scheduling/schedule";
import scheduleApis from "@/types/scheduling/scheduleApis";
export default function ScheduleTemplateEditForm({
@@ -49,13 +60,26 @@ export default function ScheduleTemplateEditForm({
facilityId: string;
userId: string;
}) {
+ const { t } = useTranslation();
+
return (
-
+
+
+
+
{t("availabilities")}
+
+
+ {template.availabilities.length === 0 && (
+
+
{t("no_availabilities_yet")}
+
+ )}
+
{template.availabilities.map((availability) => (
))}
+
+
);
}
@@ -144,7 +174,7 @@ const ScheduleTemplateEditor = ({
)}
/>
-
+
{
- toast.success(t("schedule_availability_updated_successfully"));
- queryClient.invalidateQueries({
- queryKey: ["user-schedule-templates", { facilityId, userId }],
- });
- },
- });
-
const { mutate: deleteAvailability, isPending: isDeleting } = useMutation({
mutationFn: mutate(scheduleApis.templates.availabilities.delete, {
pathParams: {
@@ -233,58 +247,6 @@ const AvailabilityEditor = ({
},
});
- return availability.slot_type === "appointment" ? (
-
- ) : (
-
- );
-};
-
-const AppointmentAvailabilityEditor = ({
- availability,
- updateAvailability,
- deleteAvailability,
- isProcessing,
-}: {
- availability: ScheduleAvailability & { slot_type: "appointment" };
- updateAvailability: (request: ScheduleAvailabilityUpdateRequest) => void;
- deleteAvailability: () => void;
- isProcessing: boolean;
-}) => {
- const { t } = useTranslation();
- const appointmentAvailabilityFormSchema = z.object({
- name: z.string().min(1, t("field_required")),
- tokens_per_slot: z.number().min(1, t("number_min_error", { min: 1 })),
- reason: z.string(),
- });
-
- const form = useForm>({
- resolver: zodResolver(appointmentAvailabilityFormSchema),
- defaultValues: {
- name: availability.name,
- tokens_per_slot: availability.tokens_per_slot,
- reason: availability.reason || "",
- },
- });
-
- function onSubmit(values: z.infer) {
- updateAvailability({
- name: values.name,
- tokens_per_slot: values.tokens_per_slot,
- reason: values.reason,
- });
- }
-
// Group availabilities by day of week
const availabilitiesByDay = availability.availability.reduce(
(acc, curr) => {
@@ -298,218 +260,272 @@ const AppointmentAvailabilityEditor = ({
{} as Record,
);
- // Calculate total slots
- const totalSlots = Math.floor(
- getSlotsPerSession(
- availability.availability[0].start_time,
- availability.availability[0].end_time,
+ // Calculate slots and duration for appointment type
+ const { totalSlots, tokenDuration } = (() => {
+ if (availability.slot_type !== "appointment")
+ return { totalSlots: null, tokenDuration: null };
+
+ 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,
- ) ?? 0,
- );
+ availability.tokens_per_slot,
+ );
+
+ return { totalSlots: slots, tokenDuration: duration };
+ })();
return (
-
+
-
-
{availability.name}
-
- {availability.slot_type}
- |
- {totalSlots} slots
- |
- {availability.slot_size_in_minutes} min.
-
-
+
{availability.name}
+
+ {t(`SCHEDULE_AVAILABILITY_TYPE__${availability.slot_type}`)}
+
-
-
-
-
-
- deleteAvailability()}
- disabled={isProcessing}
- className="text-red-600"
- >
-
- {t("delete")}
-
-
-
+
-