diff --git a/public/locale/en.json b/public/locale/en.json index 1b013fa9254..2f5ff0b92c0 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -191,11 +191,14 @@ "ROUNDS_TYPE__NORMAL": "Brief Update", "ROUNDS_TYPE__TELEMEDICINE": "Tele-medicine Log", "ROUNDS_TYPE__VENTILATOR": "Detailed Update", + "SCHEDULE_AVAILABILITY_TYPE_DESCRIPTION__appointment": "Patients can be booked for slots in this session", + "SCHEDULE_AVAILABILITY_TYPE_DESCRIPTION__closed": "Indicates the practitioner is not available for this session's time", + "SCHEDULE_AVAILABILITY_TYPE_DESCRIPTION__open": "Indicates the practitioner is available in this session", + "SCHEDULE_AVAILABILITY_TYPE__appointment": "Appointment", + "SCHEDULE_AVAILABILITY_TYPE__closed": "Closed", + "SCHEDULE_AVAILABILITY_TYPE__open": "Open", "SCHEDULE_EXCEPTION_TYPE__MODIFY_SCHEDULE": "Modify Schedule", "SCHEDULE_EXCEPTION_TYPE__UNAVAILABLE": "Unavailable", - "SCHEDULE_SLOT_TYPE__appointment": "Appointment", - "SCHEDULE_SLOT_TYPE__closed": "Closed", - "SCHEDULE_SLOT_TYPE__open": "Open", "SLEEP__EXCESSIVE": "Excessive", "SLEEP__NO_SLEEP": "No sleep", "SLEEP__SATISFACTORY": "Satisfactory", @@ -313,6 +316,7 @@ "active_files": "Active Files", "active_prescriptions": "Active Prescriptions", "add": "Add", + "add_another_session": "Add another session", "add_as": "Add as", "add_attachments": "Add Attachments", "add_beds": "Add Bed(s)", @@ -320,6 +324,7 @@ "add_consultation": "Add consultation", "add_consultation_update": "Add Consultation Update", "add_details_of_patient": "Add Details of Patient", + "add_exception": "Add Exception", "add_facility": "Add Facility", "add_files": "Add Files", "add_insurance_details": "Add Insurance Details", @@ -337,6 +342,8 @@ "add_prn_prescription": "Add PRN Prescription", "add_questionnaire": "Add Questionnaire", "add_remarks": "Add remarks", + "add_schedule_exceptions": "Add Schedule Exceptions", + "add_schedule_exceptions_description": "Configure absences or add availability beyond the regular schedule.", "add_skill": "Add Skill", "add_spoke": "Add Spoke Facility", "additional_information": "Additional Information", @@ -351,6 +358,7 @@ "admitted": "Admitted", "admitted_on": "Admitted On", "advanced_filters": "Advanced Filters", + "after": "after", "age": "Age", "age_input_warning": "While entering a patient's age is an option, please note that only the year of birth will be captured from this information.", "age_input_warning_bold": "Recommended only when the patient's date of birth is unknown", @@ -435,6 +443,8 @@ "auth_method_unsupported": "This authentication method is not supported, please try a different method", "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", @@ -461,6 +471,7 @@ "bed_type__300": "Oxygen Supported Bed", "bed_type__400": "Isolation Bed", "bed_type__500": "Others", + "before": "before", "beta": "beta", "bladder": "Bladder", "blood_group": "Blood Group", @@ -484,6 +495,7 @@ "camera_was_linked_to_bed": "This camera was linked to this bed", "cancel": "Cancel", "cancel_appointment": "Cancel Appointment", + "cancel_appointment_warning": "This action cannot be undone. The appointment will be cancelled and the patient will be notified.", "cancelled": "Cancelled", "cannot_select_date_out_of_range": "Cannot select date out of range", "cannot_select_month_out_of_range": "Cannot select month out of range", @@ -576,6 +588,7 @@ "confirm_password": "Confirm Password", "confirm_password_required": "Confirm password is required", "confirm_transfer_complete": "Confirm Transfer Complete!", + "confirm_unavailability": "Confirm Unavailability", "confirmed": "Confirmed", "consent__hi_range": "Health Information Range", "consent__hi_type__DiagnosticReport": "Diagnostic Report", @@ -656,11 +669,14 @@ "create_position_preset_description": "Creates a new position preset in Care from the current position of the camera for the given name", "create_preset_prerequisite": "To create presets for this bed, you'll need to link the camera to the bed first.", "create_resource_request": "Create Request", + "create_schedule_template": "Create Schedule Template", + "create_template": "Create Template", "create_user": "Create User", "created": "Created", "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", @@ -685,6 +701,7 @@ "date_of_result": "Covid confirmation date", "date_of_return": "Date of Return", "date_of_test": "Date of sample collection for Covid testing", + "date_range": "Date Range", "day": "Day", "death_report": "Death Report", "delete": "Delete", @@ -695,6 +712,7 @@ "delete_item": "Delete {{name}}", "delete_record": "Delete Record", "deleted_successfully": "{{name}} deleted successfully", + "deleting": "Deleting...", "demography": "Demography", "denied_on": "Denied On", "describe_why_the_asset_is_not_working": "Describe why the asset is not working", @@ -776,6 +794,7 @@ "edit_prescriptions": "Edit Prescriptions", "edit_profile": "Edit Profile", "edit_role": "Edit Role", + "edit_schedule_template": "Edit Schedule Template", "edit_user_profile": "Edit Profile", "edit_user_role": "Edit User Role", "edited_by": "Edited by", @@ -895,6 +914,7 @@ "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation", "encounters": "Encounters", "end_datetime": "End Date/Time", + "end_time": "End Time", "enter_aadhaar_number": "Enter a 12-digit Aadhaar ID", "enter_aadhaar_otp": "Enter OTP sent to the registered mobile with Aadhaar", "enter_abha_address": "Enter ABHA Address", @@ -912,6 +932,7 @@ "enter_year_of_birth_to_verify": "Enter year of birth to verify", "entered-in-error": "Entered in error", "entered_in_error": "Entered in Error", + "entered_in_error_warning": "This action cannot be undone. The appointment will be marked as entered in error and removed from the system.", "error_404": "Error 404", "error_deleting_shifting": "Error while deleting Shifting record", "error_fetching_facility_data": "Error while fetching facility data", @@ -927,6 +948,7 @@ "etiology_identified": "Etiology identified", "evening_slots": "Evening Slots", "events": "Events", + "exception_created": "Exception created successfully", "exception_deleted": "Exception deleted", "exceptions": "Exceptions", "expand_sidebar": "Expand Sidebar", @@ -996,6 +1018,7 @@ "forget_password_instruction": "Enter your username, and if it exists, we will send you a link to reset your password.", "frequency": "Frequency", "from": "from", + "from_date_must_be_before_to_date": "From date must be before to date", "from_user": "from User", "fulfilled": "Fulfilled", "full_name": "Full Name", @@ -1136,6 +1159,7 @@ "last_administered": "Last administered", "last_discharge_reason": "Last Discharge Reason", "last_edited": "Last Edited", + "last_fortnight_short": "Last 2wk", "last_login": "Last Login", "last_modified": "Last Modified", "last_modified_by": "Last Modified By", @@ -1145,6 +1169,7 @@ "last_serviced_on": "Last Serviced On", "last_updated_by": "Last updated by", "last_vaccinated_on": "Last Vaccinated on", + "last_week_short": "Last wk", "latitude_invalid": "Latitude must be between -90 and 90", "left": "Left", "length": "Length ({{unit}})", @@ -1196,6 +1221,7 @@ "map_acronym": "M.A.P.", "mark_all_as_read": "Mark all as Read", "mark_as_complete": "Mark as Complete", + "mark_as_entered_in_error": "Mark as entered in error", "mark_as_fulfilled": "Mark as Fullfilled", "mark_as_noshow": "Mark as no-show", "mark_as_read": "Mark as Read", @@ -1247,6 +1273,7 @@ "moving_camera": "Moving Camera", "my_doctors": "My Doctors", "my_profile": "My Profile", + "my_schedules": "My Schedules", "name": "Name", "name_of_hospital": "Name of Hospital", "name_of_shifting_approving_facility": "Name of shifting approving facility", @@ -1259,11 +1286,15 @@ "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_fortnight_short": "Next 2wk", "next_sessions": "Next Sessions", + "next_week_short": "Next wk", "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", @@ -1312,6 +1343,7 @@ "no_resource_requests_found": "No requests found", "no_results": "No results", "no_results_found": "No Results Found", + "no_schedule_templates_found": "No schedule templates found for this month.", "no_scheduled_exceptions_found": "No scheduled exceptions found", "no_slots_available": "No slots available", "no_slots_available_for_this_date": "No slots available for this date", @@ -1337,6 +1369,7 @@ "notification_permission_denied": "Notification permission denied", "notification_permission_granted": "Notification permission granted", "notify": "Notify", + "number_min_error": "Must be greater than {{min}}", "number_of_aged_dependents": "Number of Aged Dependents (Above 60)", "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", @@ -1445,6 +1478,7 @@ "patient_update_error": "Could not update patient", "patient_update_success": "Patient Updated Sucessfully", "patients": "Patients", + "patients_per_slot": "Patients per Slot", "pending": "Pending", "permanent_address": "Permanent Address", "permission_denied": "You do not have permission to perform this action", @@ -1579,6 +1613,8 @@ "reject": "Reject", "rejected": "Rejected", "reload": "Reload", + "remarks": "Remarks", + "remarks_placeholder": "Enter remarks", "remove": "Remove", "remove_user": "Remove User", "remove_user_organization": "Remove User from Organization", @@ -1608,6 +1644,7 @@ "requested_by": "Requested By", "required": "Required", "required_quantity": "Required Quantity", + "reschedule": "Reschedule", "resend_otp": "Resend OTP", "reset": "Reset", "reset_password": "Reset Password", @@ -1655,10 +1692,30 @@ "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_calendar": "Schedule Calendar", + "schedule_end_time": "End Time", + "schedule_for": "Scheduled for", "schedule_information": "Schedule Information", + "schedule_remarks": "Remarks", + "schedule_remarks_placeholder": "Any additional notes about this session", + "schedule_session_time": "Session Time", + "schedule_session_type": "Session Type", + "schedule_sessions": "Sessions", + "schedule_sessions_min_error": "Add at least one session", + "schedule_slot_size_label": "Slot duration (mins.)", + "schedule_slots_allocation_callout": "Allocating {{slots}} slots in this session provides approximately {{token_duration}} mins. for each patient.", + "schedule_start_time": "Start Time", + "schedule_template": "Schedule Template", + "schedule_template_name": "Template Name", + "schedule_template_name_placeholder": "Regular OP Day", + "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", + "schedule_weekdays_min_error": "Select at least one weekday", "scheduled": "Scheduled", - "schedules": "Schedules", + "scheduled_for": "Schedule for:", "scribe__reviewing_field": "Reviewing field {{currentField}} / {{totalFields}}", "scribe_error": "Could not autofill fields", "search": "Search", @@ -1721,8 +1778,12 @@ "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", + "session_title_placeholder": "IP Rounds", + "session_type": "Session Type", "set_average_weekly_working_hours_for": "Set Average weekly working hours for", "set_home_facility": "Set as home facility", "set_your_local_language": "Set your local language", @@ -1746,11 +1807,13 @@ "show_default_presets": "Show Default Presets", "show_patient_presets": "Show Patient Presets", "show_unread_notifications": "Show Unread", + "showing_all_appointments": "Showing all appointments", "sign_in": "Sign in", "sign_out": "Sign out", "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.", @@ -1769,6 +1832,9 @@ "start_consultation": "Start Consultation", "start_datetime": "Start Date/Time", "start_dosage": "Start Dosage", + "start_review": "Start Review", + "start_time": "Start Time", + "start_time_must_be_before_end_time": "Start time must be before end time", "state": "State", "status": "Status", "stop": "Stop", @@ -1796,6 +1862,7 @@ "systolic": "Systolic", "tachycardia": "Tachycardia", "target_dosage": "Target Dosage", + "template_deleted": "Template has been deleted", "test_type": "Type of test done", "tested_on": "Tested on", "thank_you_for_choosing": "Thank you for choosing our care service", @@ -1812,9 +1879,11 @@ "today": "Today", "token": "Token", "token_no": "Token No.", + "tomorrow": "Tomorrow", "total_amount": "Total Amount", "total_beds": "Total Beds", "total_patients": "Total Patients", + "total_slots": "Total Slots", "total_staff": "Total Staff", "total_users": "Total Users", "transfer_allowed": "Transfer Allowed", @@ -1928,7 +1997,9 @@ "vacant": "Vacant", "vaccinated": "Vaccinated", "vaccine_name": "Vaccine name", + "valid_from": "Valid From", "valid_otp_found": "Valid OTP found, Navigating to Appointments", + "valid_to": "Valid Till", "valid_year_of_birth": "Please enter a valid year of birth (YYYY)", "vehicle_preference": "Vehicle preference", "vendor_name": "Vendor Name", @@ -1982,8 +2053,10 @@ "volunteer_update": "Volunteer updated successfully", "waitlist": "Waitlist", "ward": "Ward", + "warning": "Warning", "warranty_amc_expiry": "Warranty / AMC Expiry", "we_ve_sent_you_a_code_to": "We've sent you a code to", + "weekly_schedule": "Weekly Schedule", "weekly_working_hours_error": "Average weekly working hours must be a number between 0 and 168", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", "whatsapp_number": "Whatsapp Number", @@ -1998,6 +2071,7 @@ "years_of_experience": "Years of Experience", "years_of_experience_of_the_doctor": "Years of Experience of the Doctor", "yes": "Yes", + "yesterday": "Yesterday", "yet_to_be_decided": "Yet to be decided", "you_need_at_least_a_location_to_create_an_assest": "You need at least a location to create an assest.", "zoom_in": "Zoom In", diff --git a/src/CAREUI/display/Callout.tsx b/src/CAREUI/display/Callout.tsx index c1d4b05a88f..c14a471be7d 100644 --- a/src/CAREUI/display/Callout.tsx +++ b/src/CAREUI/display/Callout.tsx @@ -16,7 +16,7 @@ export default function Callout({ return (
{props.badge}
- {props.children} +
+ {props.children} +
); } diff --git a/src/CAREUI/interactive/WeekdayCheckbox.tsx b/src/CAREUI/interactive/WeekdayCheckbox.tsx index 1660d14ef02..1d89fc30c1d 100644 --- a/src/CAREUI/interactive/WeekdayCheckbox.tsx +++ b/src/CAREUI/interactive/WeekdayCheckbox.tsx @@ -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 ( - + ); } diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index a49ca7767f9..ed8ed8a8aa0 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -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"; @@ -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"; diff --git a/src/Routers/PatientRouter.tsx b/src/Routers/PatientRouter.tsx index bf16c8c3e30..cc07a27d887 100644 --- a/src/Routers/PatientRouter.tsx +++ b/src/Routers/PatientRouter.tsx @@ -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"; diff --git a/src/Routers/PublicRouter.tsx b/src/Routers/PublicRouter.tsx index 47739e6ff37..9128f404b2a 100644 --- a/src/Routers/PublicRouter.tsx +++ b/src/Routers/PublicRouter.tsx @@ -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")); diff --git a/src/components/Schedule/routes.tsx b/src/Routers/routes/ScheduleRoutes.tsx similarity index 65% rename from src/components/Schedule/routes.tsx rename to src/Routers/routes/ScheduleRoutes.tsx index c784bc03c05..4410009a331 100644 --- a/src/components/Schedule/routes.tsx +++ b/src/Routers/routes/ScheduleRoutes.tsx @@ -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(); @@ -25,16 +24,13 @@ const ScheduleRoutes: AppRoutes = { "/facility/:facilityId/patient/:patientId/book-appointment": ({ facilityId, patientId, - }) => , + }) => , "/facility/:facilityId/patient/:patientId/appointments/:appointmentId": ({ facilityId, appointmentId, }) => ( - + ), }; diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index ec3643ce9f7..73363218800 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -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[] { @@ -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, }); }); diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index c821bbbaa08..24c492633d3 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -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); diff --git a/src/Utils/types.ts b/src/Utils/types.ts index 22da8867b61..cdee60e1f2f 100644 --- a/src/Utils/types.ts +++ b/src/Utils/types.ts @@ -48,4 +48,4 @@ export type WritableOnly = T extends object type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; -export type Time = `${number}:${number}`; +export type Time = `${number}:${number}` | `${number}:${number}:${number}`; diff --git a/src/components/Facility/FacilityUsers.tsx b/src/components/Facility/FacilityUsers.tsx index a4af2733d83..4326cc2d618 100644 --- a/src/components/Facility/FacilityUsers.tsx +++ b/src/components/Facility/FacilityUsers.tsx @@ -82,7 +82,7 @@ export default function FacilityUsers(props: { facilityId: number }) { } return ( - + & { }; /** - * A FormField to pick date. - * - * Example usage: - * - * ```jsx - * - * ``` + * @deprecated use shadcn/ui's date-picker instead */ const DateFormField = (props: Props) => { const field = useFormFieldPropsResolver(props); diff --git a/src/components/Form/FormFields/FormField.tsx b/src/components/Form/FormFields/FormField.tsx index 7f9c2699d64..f3ad0559e44 100644 --- a/src/components/Form/FormFields/FormField.tsx +++ b/src/components/Form/FormFields/FormField.tsx @@ -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 diff --git a/src/components/Form/FormFields/RadioFormField.tsx b/src/components/Form/FormFields/RadioFormField.tsx index 79cdb64a579..ca205fcccad 100644 --- a/src/components/Form/FormFields/RadioFormField.tsx +++ b/src/components/Form/FormFields/RadioFormField.tsx @@ -17,6 +17,9 @@ type Props = FormFieldBaseProps & { layout?: "vertical" | "horizontal" | "grid" | "auto"; }; +/** + * @deprecated use shadcn/ui's radio-group instead + */ const RadioFormField = (props: Props) => { const field = useFormFieldPropsResolver(props); return ( diff --git a/src/components/Form/FormFields/SelectFormField.tsx b/src/components/Form/FormFields/SelectFormField.tsx index 5cf992d8bdd..aa712dc16a1 100644 --- a/src/components/Form/FormFields/SelectFormField.tsx +++ b/src/components/Form/FormFields/SelectFormField.tsx @@ -21,6 +21,9 @@ type SelectFormFieldProps = FormFieldBaseProps & { inputClassName?: string; }; +/** + * @deprecated use shadcn/ui's select instead + */ export const SelectFormField = (props: SelectFormFieldProps) => { const field = useFormFieldPropsResolver(props); return ( @@ -58,6 +61,9 @@ type MultiSelectFormFieldProps = FormFieldBaseProps & { optionDisabled?: OptionCallback; }; +/** + * @deprecated + */ export const MultiSelectFormField = ( props: MultiSelectFormFieldProps, ) => { diff --git a/src/components/Form/FormFields/TextAreaFormField.tsx b/src/components/Form/FormFields/TextAreaFormField.tsx index f26717810d4..b4e85e226ea 100644 --- a/src/components/Form/FormFields/TextAreaFormField.tsx +++ b/src/components/Form/FormFields/TextAreaFormField.tsx @@ -19,6 +19,9 @@ export type TextAreaFormFieldProps = FormFieldBaseProps & { onBlur?: (event: React.FocusEvent) => void; }; +/** + * @deprecated use shadcn/ui's textarea instead + */ const TextAreaFormField = forwardRef( ( { rows = 3, ...props }: TextAreaFormFieldProps, diff --git a/src/components/Form/FormFields/TextFormField.tsx b/src/components/Form/FormFields/TextFormField.tsx index c9662f83917..8f816c31a2a 100644 --- a/src/components/Form/FormFields/TextFormField.tsx +++ b/src/components/Form/FormFields/TextFormField.tsx @@ -32,6 +32,9 @@ export type TextFormFieldProps = FormFieldBaseProps & clearable?: boolean | undefined; }; +/** + * @deprecated use shadcn/ui's Input instead + */ const TextFormField = forwardRef((props: TextFormFieldProps, ref) => { const field = useFormFieldPropsResolver(props); const { leading, trailing } = props; diff --git a/src/components/Patient/PatientDetailsTab/Appointments.tsx b/src/components/Patient/PatientDetailsTab/Appointments.tsx index 70c0f6f081e..cf1da394da3 100644 --- a/src/components/Patient/PatientDetailsTab/Appointments.tsx +++ b/src/components/Patient/PatientDetailsTab/Appointments.tsx @@ -17,10 +17,10 @@ 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; @@ -28,7 +28,7 @@ export const Appointments = (props: PatientProps) => { 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 }, }), diff --git a/src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx similarity index 90% rename from src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx rename to src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx index f0f1cd3d79f..961950122cf 100644 --- a/src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx @@ -15,19 +15,19 @@ import { import { Textarea } from "@/components/ui/textarea"; import { Avatar } from "@/components/Common/Avatar"; -import { groupSlotsByAvailability } from "@/components/Schedule/Appointments/utils"; -import { ScheduleAPIs } from "@/components/Schedule/api"; -import { FollowUpAppointmentRequest } from "@/components/Schedule/types"; import useSlug from "@/hooks/useSlug"; import query from "@/Utils/request/query"; import { dateQueryString, formatDisplayName } from "@/Utils/utils"; +import { groupSlotsByAvailability } from "@/pages/Appointments/utils"; import { QuestionnaireResponse, ResponseValue, } from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; +import { CreateAppointmentQuestion } from "@/types/scheduling/schedule"; +import scheduleApis from "@/types/scheduling/scheduleApis"; import { UserBase } from "@/types/user/user"; interface FollowUpVisitQuestionProps { @@ -37,7 +37,7 @@ interface FollowUpVisitQuestionProps { disabled?: boolean; } -export function FollowUpAppointmentQuestion({ +export function AppointmentQuestion({ questionnaireResponse, updateQuestionnaireResponseCB, disabled, @@ -48,18 +48,18 @@ export function FollowUpAppointmentQuestion({ const values = (questionnaireResponse.values?.[0] - ?.value as unknown as FollowUpAppointmentRequest[]) || []; + ?.value as unknown as CreateAppointmentQuestion[]) || []; const value = values[0] ?? {}; - const handleUpdate = (updates: Partial) => { - const followUpAppointment = { ...value, ...updates }; + const handleUpdate = (updates: Partial) => { + const appointment = { ...value, ...updates }; updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ { - type: "follow_up_appointment", - value: [followUpAppointment] as unknown as ResponseValue["value"], + type: "appointment", + value: [appointment] as unknown as ResponseValue["value"], }, ], }); @@ -69,7 +69,7 @@ export function FollowUpAppointmentQuestion({ const resourcesQuery = useQuery({ queryKey: ["availableResources", facilityId], - queryFn: query(ScheduleAPIs.appointments.availableUsers, { + queryFn: query(scheduleApis.appointments.availableUsers, { pathParams: { facility_id: facilityId }, }), }); @@ -81,7 +81,7 @@ export function FollowUpAppointmentQuestion({ resource?.id, dateQueryString(selectedDate), ], - queryFn: query(ScheduleAPIs.slots.getSlotsForDay, { + queryFn: query(scheduleApis.slots.getSlotsForDay, { pathParams: { facility_id: facilityId }, body: { user: resource?.id, diff --git a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx index fffdb85c973..0f09a72f62e 100644 --- a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx +++ b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx @@ -3,7 +3,7 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; import { QuestionLabel } from "@/components/Questionnaire/QuestionLabel"; -import { FollowUpAppointmentQuestion } from "@/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion"; +import { AppointmentQuestion } from "@/components/Questionnaire/QuestionTypes/AppointmentQuestion"; import { QuestionValidationError } from "@/types/questionnaire/batch"; import type { @@ -118,8 +118,8 @@ export function QuestionInput({ return ; case "diagnosis": return ; - case "follow_up_appointment": - return ; + case "appointment": + return ; case "encounter": if (encounterId) { return ( diff --git a/src/components/Questionnaire/QuestionnaireEditor.tsx b/src/components/Questionnaire/QuestionnaireEditor.tsx index 8c7f0fa0854..c6101d99f1b 100644 --- a/src/components/Questionnaire/QuestionnaireEditor.tsx +++ b/src/components/Questionnaire/QuestionnaireEditor.tsx @@ -673,9 +673,7 @@ function QuestionEditor({ Symptom Diagnosis Encounter - - Follow-up Appointment - + Appointment diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 5d11c1b7359..8e5fdba0078 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -147,9 +147,9 @@ const handlers: { }); }, }, - follow_up_appointment: { - getRequests: (followUpAppointment, { facilityId, patientId }) => { - const { reason_for_visit, slot_id } = followUpAppointment[0]; + appointment: { + getRequests: (appointment, { facilityId, patientId }) => { + const { reason_for_visit, slot_id } = appointment[0]; return [ { url: `/api/v1/facility/${facilityId}/slots/${slot_id}/create_appointment/`, @@ -158,7 +158,7 @@ const handlers: { reason_for_visit, patient: patientId, }, - reference_id: "follow_up_appointment", + reference_id: "appointment", }, ]; }, diff --git a/src/components/Questionnaire/structured/types.ts b/src/components/Questionnaire/structured/types.ts index 7f31f61ad4c..e871f8bdb6d 100644 --- a/src/components/Questionnaire/structured/types.ts +++ b/src/components/Questionnaire/structured/types.ts @@ -1,8 +1,3 @@ -import { - AppointmentCreate, - FollowUpAppointmentRequest, -} from "@/components/Schedule/types"; - import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import { Diagnosis, DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; @@ -10,6 +5,10 @@ import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatement } from "@/types/emr/medicationStatement"; import { Symptom, SymptomRequest } from "@/types/emr/symptom/symptom"; import { StructuredQuestionType } from "@/types/questionnaire/question"; +import { + AppointmentCreateRequest, + CreateAppointmentQuestion, +} from "@/types/scheduling/schedule"; // Map structured types to their data types export interface StructuredDataMap { @@ -19,7 +18,7 @@ export interface StructuredDataMap { symptom: Symptom; diagnosis: Diagnosis; encounter: Encounter; - follow_up_appointment: FollowUpAppointmentRequest; + appointment: CreateAppointmentQuestion; } // Map structured types to their request types @@ -30,7 +29,7 @@ export interface StructuredRequestMap { symptom: SymptomRequest; diagnosis: DiagnosisRequest; encounter: EncounterEditRequest; - follow_up_appointment: AppointmentCreate; + appointment: AppointmentCreateRequest; } export type RequestTypeFor = diff --git a/src/components/Schedule/ScheduleTemplateForm.tsx b/src/components/Schedule/ScheduleTemplateForm.tsx deleted file mode 100644 index 570b618e21e..00000000000 --- a/src/components/Schedule/ScheduleTemplateForm.tsx +++ /dev/null @@ -1,504 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { useMutation } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { useTranslation } 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 from "@/CAREUI/interactive/WeekdayCheckbox"; -import { DayOfWeekValue } from "@/CAREUI/interactive/WeekdayCheckbox"; - -import { Button } 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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { - Sheet, - SheetClose, - SheetContent, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet"; -import { Textarea } from "@/components/ui/textarea"; - -import { ScheduleAPIs } from "@/components/Schedule/api"; -import { - getSlotsPerSession, - getTokenDuration, -} from "@/components/Schedule/helpers"; -import { ScheduleSlotTypes } from "@/components/Schedule/types"; - -import useSlug from "@/hooks/useSlug"; - -import mutate from "@/Utils/request/mutate"; -import { Time } from "@/Utils/types"; -import { dateQueryString } from "@/Utils/utils"; -import { UserBase } from "@/types/user/user"; - -const formSchema = z.object({ - name: z.string().min(1, "Template name is required"), - valid_from: z.date({ - required_error: "Valid from date is required", - }), - valid_to: z.date({ - required_error: "Valid to date is required", - }), - weekdays: z - .array(z.number() as unknown as z.ZodType) - .min(1, "At least one weekday is required"), - availabilities: z - .array( - z.object({ - name: z.string().min(1, "Session name is required"), - slot_type: z.enum(ScheduleSlotTypes), - reason: z.string(), - start_time: z - .string() - .min(1, "Start time is required") as unknown as z.ZodType