diff --git a/crowdin.yml b/crowdin.yml index fdcb6bcd32d..04cbe1a8d50 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,6 +1,5 @@ files: - - source: /public/locale/{{lang}}.json - translation: /public/locale/%two_letters_code%/%original_file_name% + - source: public/locale/en.json + translation: /public/locale/%two_letters_code%.json bundles: - 2 - diff --git a/public/locale/en.json b/public/locale/en.json index 2473dc49416..358a1e61ec6 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -359,6 +359,7 @@ "all_changes_have_been_saved": "All changes have been saved", "all_details": "All Details", "all_patients": "All Patients", + "allergen": "Allergen", "allergies": "Allergies", "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", @@ -652,6 +653,7 @@ "created_by": "Created By", "created_date": "Created Date", "created_on": "Created On", + "criticality": "Criticality", "csv_file_in_the_specified_format": "Select a CSV file in the specified format", "current_address": "Current Address", "current_password": "Current Password", @@ -832,6 +834,7 @@ "encounter_discharge_disposition__snf": "Skilled nursing facility", "encounter_duration_confirmation": "The duration of this encounter would be", "encounter_id": "Encounter ID", + "encounter_marked_as_complete": "Encounter Completed", "encounter_notes__all_discussions": "All Discussions", "encounter_notes__be_first_to_send": "Be the first to send a message", "encounter_notes__choose_template": "Choose a template or enter a custom title", @@ -902,6 +905,7 @@ "error_deleting_shifting": "Error while deleting Shifting record", "error_fetching_slots_data": "Error while fetching slots data", "error_sending_otp": "Error while sending OTP, Please try again later", + "error_updating_encounter": "Error to Updating Encounter", "error_verifying_otp": "Error while verifying OTP, Please request a new OTP", "error_while_deleting_record": "Error while deleting record", "escape": "Escape", @@ -1166,6 +1170,7 @@ "manufacturer": "Manufacturer", "map_acronym": "M.A.P.", "mark_all_as_read": "Mark all as Read", + "mark_as_complete": "Mark as Complete", "mark_as_fulfilled": "Mark as Fullfilled", "mark_as_noshow": "Mark as no-show", "mark_as_read": "Mark as Read", @@ -1631,6 +1636,7 @@ "select": "Select", "select_all": "Select All", "select_date": "Select date", + "select_department": "Select Department", "select_eligible_policy": "Select an Eligible Insurance Policy", "select_facility_for_discharged_patients_warning": "Facility needs to be selected to view discharged patients.", "select_for_administration": "Select for Administration", diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index 2f657b67fc6..7b94ba73e4a 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -1,6 +1,5 @@ import careConfig from "@careConfig"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import dayjs from "dayjs"; import { navigate } from "raviger"; import { useCallback, useEffect, useState } from "react"; @@ -13,6 +12,7 @@ import { LocalStorageKeys } from "@/common/constants"; import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; +import { TokenData } from "@/types/auth/otpToken"; interface Props { children: React.ReactNode; @@ -29,8 +29,10 @@ export default function AuthUserProvider({ const [accessToken, setAccessToken] = useState( localStorage.getItem(LocalStorageKeys.accessToken), ); - const [patientToken, setPatientToken] = useState( - JSON.parse(localStorage.getItem(LocalStorageKeys.patientTokenKey) || "{}"), + const [patientToken, setPatientToken] = useState( + JSON.parse( + localStorage.getItem(LocalStorageKeys.patientTokenKey) || "null", + ), ); const { data: user, isLoading } = useQuery({ @@ -40,16 +42,6 @@ export default function AuthUserProvider({ enabled: !!localStorage.getItem(LocalStorageKeys.accessToken), }); - useEffect(() => { - if ( - patientToken.token && - Object.keys(patientToken).length > 0 && - dayjs(patientToken.createdAt).isAfter(dayjs().subtract(14, "minutes")) - ) { - navigate("/patient/home"); - } - }, [patientToken]); - useEffect(() => { if (!user) { return; @@ -83,20 +75,20 @@ export default function AuthUserProvider({ [queryClient], ); - const patientLogin = useCallback(() => { - setPatientToken( - JSON.parse( - localStorage.getItem(LocalStorageKeys.patientTokenKey) || "{}", - ), + const patientLogin = (tokenData: TokenData, redirectUrl: string) => { + setPatientToken(tokenData); + localStorage.setItem( + LocalStorageKeys.patientTokenKey, + JSON.stringify(tokenData), ); - navigate("/patient/home"); - }, []); + navigate(redirectUrl); + }; const signOut = useCallback(async () => { localStorage.removeItem(LocalStorageKeys.accessToken); localStorage.removeItem(LocalStorageKeys.refreshToken); localStorage.removeItem(LocalStorageKeys.patientTokenKey); - setPatientToken({}); + setPatientToken(null); await queryClient.resetQueries({ queryKey: ["currentUser"] }); @@ -134,7 +126,7 @@ export default function AuthUserProvider({ const SelectedRouter = () => { if (user) { return children; - } else if (patientToken.token) { + } else if (patientToken?.token) { return otpAuthorized; } else { return unauthorized; @@ -148,6 +140,7 @@ export default function AuthUserProvider({ signOut, user, patientLogin, + patientToken, }} > diff --git a/src/Providers/PatientUserProvider.tsx b/src/Providers/PatientUserProvider.tsx index 3ec0c5f038a..1ae9a9af8f1 100644 --- a/src/Providers/PatientUserProvider.tsx +++ b/src/Providers/PatientUserProvider.tsx @@ -1,20 +1,14 @@ import { useQuery } from "@tanstack/react-query"; +import { navigate } from "raviger"; import { createContext, useEffect, useState } from "react"; -import { SidebarProvider } from "@/components/ui/sidebar"; -import { AppSidebar } from "@/components/ui/sidebar/app-sidebar"; - -import { LocalStorageKeys } from "@/common/constants"; +import { useAuthContext } from "@/hooks/useAuthUser"; import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { AppointmentPatient } from "@/pages/Patient/Utils"; import { TokenData } from "@/types/auth/otpToken"; -const tokenData: TokenData = JSON.parse( - localStorage.getItem(LocalStorageKeys.patientTokenKey) || "{}", -); - export type PatientUserContextType = { patients?: AppointmentPatient[]; selectedPatient: AppointmentPatient | null; @@ -22,12 +16,9 @@ export type PatientUserContextType = { tokenData: TokenData; }; -export const PatientUserContext = createContext({ - patients: undefined, - selectedPatient: null, - setSelectedPatient: () => {}, - tokenData: tokenData, -}); +export const PatientUserContext = createContext( + null, +); interface Props { children: React.ReactNode; @@ -38,14 +29,16 @@ export default function PatientUserProvider({ children }: Props) { const [selectedPatient, setSelectedPatient] = useState(null); + const { patientToken: tokenData } = useAuthContext(); + const { data: userData } = useQuery({ - queryKey: ["patients", tokenData.phoneNumber], + queryKey: ["patients", tokenData], queryFn: query(routes.otp.getPatient, { headers: { - Authorization: `Bearer ${tokenData.token}`, + Authorization: `Bearer ${tokenData?.token}`, }, }), - enabled: !!tokenData.token, + enabled: !!tokenData?.token, }); useEffect(() => { @@ -62,22 +55,21 @@ export default function PatientUserProvider({ children }: Props) { } }, [userData]); - const patientUserContext: PatientUserContextType = { - patients, - selectedPatient, - setSelectedPatient, - tokenData, - }; + if (!tokenData) { + navigate("/"); + return null; + } return ( - - - - {children} - + + {children} ); } diff --git a/src/Routers/PatientRouter.tsx b/src/Routers/PatientRouter.tsx index ded69ce375d..bf16c8c3e30 100644 --- a/src/Routers/PatientRouter.tsx +++ b/src/Routers/PatientRouter.tsx @@ -1,7 +1,8 @@ import careConfig from "@careConfig"; import { useRoutes } from "raviger"; -import { SidebarTrigger } from "@/components/ui/sidebar"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar, SidebarFor } from "@/components/ui/sidebar/app-sidebar"; import ErrorBoundary from "@/components/Common/ErrorBoundary"; import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; @@ -9,13 +10,16 @@ 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 PublicRouter from "./PublicRouter"; -const PatientRoutes = { +const DashboardRoutes = { "/nearby_facilities": () => , "/facility/:facilityId/appointments/:appointmentId/success": ({ appointmentId, @@ -35,43 +39,75 @@ const PatientRoutes = { }) => , }; +const AppointmentRoutes = { + "/facility/:facilityId/appointments/:staffId/book-appointment": ({ + facilityId, + staffId, + }: { + facilityId: string; + staffId: string; + }) => , + "/facility/:facilityId/appointments/:staffId/patient-select": ({ + facilityId, + staffId, + }: { + facilityId: string; + staffId: string; + }) => , + "/facility/:facilityId/appointments/:staffId/patient-registration": ({ + facilityId, + staffId, + }: { + facilityId: string; + staffId: string; + }) => , +}; + export default function PatientRouter() { - const pages = useRoutes(PatientRoutes); + const pages = useRoutes(DashboardRoutes); + + const appointmentPages = useRoutes(AppointmentRoutes); if (!pages) { + if (appointmentPages) { + return {appointmentPages}; + } return ; } return ( -
-
-
- + + +
+
+
+ +
+ + care logo +
- - care logo - -
-
- }> - {pages} - -
-
+ }> + {pages} + + + +
); } diff --git a/src/Routers/PublicRouter.tsx b/src/Routers/PublicRouter.tsx index 62c67eb8cbf..47739e6ff37 100644 --- a/src/Routers/PublicRouter.tsx +++ b/src/Routers/PublicRouter.tsx @@ -6,9 +6,6 @@ import ResetPassword from "@/components/Auth/ResetPassword"; import InvalidReset from "@/components/ErrorPages/InvalidReset"; import SessionExpired from "@/components/ErrorPages/SessionExpired"; -import { PatientRegistration } from "@/pages/Appoinments/PatientRegistration"; -import PatientSelect from "@/pages/Appoinments/PatientSelect"; -import { ScheduleAppointment } from "@/pages/Appoinments/Schedule"; import PatientLogin from "@/pages/Appoinments/auth/PatientLogin"; import { FacilitiesPage } from "@/pages/Facility/FacilitiesPage"; import { FacilityDetailsPage } from "@/pages/Facility/FacilityDetailsPage"; @@ -29,27 +26,6 @@ export const routes = { staffId: string; page: string; }) => , - "/facility/:facilityId/appointments/:staffId/book-appointment": ({ - facilityId, - staffId, - }: { - facilityId: string; - staffId: string; - }) => , - "/facility/:facilityId/appointments/:staffId/patient-select": ({ - facilityId, - staffId, - }: { - facilityId: string; - staffId: string; - }) => , - "/facility/:facilityId/appointments/:staffId/patient-registration": ({ - facilityId, - staffId, - }: { - facilityId: string; - staffId: string; - }) => , "/login": () => , "/forgot-password": () => , "/password_reset/:token": ({ token }: { token: string }) => ( diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index a55b00b3513..96812b5ea40 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -42,26 +42,6 @@ const consultationRoutes: AppRoutes = { patientId={patientId} /> ), - "/facility/:facilityId/patient/:patientId/consultation/:id/consent-records": - ({ facilityId, patientId, id }) => ( - - ), - "/facility/:facilityId/patient/:patientId/encounterId/:id/files/": ({ - facilityId, - patientId, - id, - }) => ( - - ), "/facility/:facilityId/patient/:patientId/questionnaire": ({ facilityId, patientId, @@ -80,10 +60,6 @@ const consultationRoutes: AppRoutes = { patientId={patientId} /> ), - "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire_response/:id": - ({ patientId, id }) => ( - - ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire/:slug": ({ facilityId, encounterId, slug, patientId }) => ( ), + "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire_response/:id": + ({ patientId, id }) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/consent-records": + ({ facilityId, patientId, id }) => ( + + ), + "/facility/:facilityId/patient/:patientId/encounterId/:id/files/": ({ + facilityId, + patientId, + id, + }) => ( + + ), }; export default consultationRoutes; diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index d1756c79624..8858d33be0e 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -28,8 +28,6 @@ import BrowserWarning from "@/components/ErrorPages/BrowserWarning"; import { useAuthContext } from "@/hooks/useAuthUser"; -import { LocalStorageKeys } from "@/common/constants"; - import FiltersCache from "@/Utils/FiltersCache"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; @@ -152,11 +150,7 @@ const Login = (props: LoginProps) => { phoneNumber: `+91${phone}`, createdAt: new Date().toISOString(), }; - localStorage.setItem( - LocalStorageKeys.patientTokenKey, - JSON.stringify(tokenData), - ); - patientLogin(); + patientLogin(tokenData, `/patient/home`); } }, onError: (error: any) => { diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index 8c0c8d29014..2c716095400 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -1,3 +1,4 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { BedSingle, Building, @@ -9,8 +10,18 @@ import { } from "lucide-react"; import { Link } from "raviger"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Popover, PopoverContent, @@ -19,13 +30,29 @@ import { import { Avatar } from "@/components/Common/Avatar"; +import routes from "@/Utils/request/api"; +import mutate from "@/Utils/request/mutate"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; import { Encounter, completedEncounterStatus } from "@/types/emr/encounter"; import { Patient } from "@/types/emr/newPatient"; -import { Button } from "../ui/button"; import ManageEncounterOrganizations from "./ManageEncounterOrganizations"; +const QUESTIONNAIRE_OPTIONS = [ + { + slug: "encounter", + title: "Update Encounter", + }, + { + slug: "community-nurse", + title: "Community Nurse Form", + }, + { + slug: "recommend_discharge_v2", + title: "Recommend Discharge", + }, +] as const; + export interface PatientInfoCardProps { patient: Patient; encounter: Encounter; @@ -35,6 +62,36 @@ export interface PatientInfoCardProps { export default function PatientInfoCard(props: PatientInfoCardProps) { const { patient, encounter } = props; const { t } = useTranslation(); + const queryClient = useQueryClient(); + + const { mutate: updateEncounter } = useMutation({ + mutationFn: mutate(routes.encounter.update, { + pathParams: { id: encounter.id }, + }), + onSuccess: () => { + toast.success(t("encounter_marked_as_complete")); + queryClient.invalidateQueries({ queryKey: ["encounter", encounter.id] }); + }, + onError: () => { + toast.error(t("error_updating_encounter")); + }, + }); + + const handleMarkAsComplete = () => { + updateEncounter({ + ...encounter, + status: "completed", + organizations: encounter.organizations.map((org) => org.id), + patient: encounter.patient.id, + encounter_class: encounter.encounter_class, + period: encounter.period, + hospitalization: encounter.hospitalization, + priority: encounter.priority, + external_identifier: encounter.external_identifier, + facility: encounter.facility.id, + }); + }; + return ( <>
@@ -262,13 +319,31 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { > {!completedEncounterStatus.includes(encounter.status) && (
- + + + + + + {QUESTIONNAIRE_OPTIONS.map((option) => ( + + + {t(option.title)} + + + ))} + + {t("actions")} + + {t("mark_as_complete")} + + +
)} diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 1dc236f5fb3..94289124403 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -8,7 +8,6 @@ import SectionNavigator from "@/CAREUI/misc/SectionNavigator"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { InputErrors } from "@/components/ui/errors"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -49,7 +48,6 @@ import OrganizationSelector from "@/pages/Organization/components/OrganizationSe import { PatientModel, validatePatient } from "@/types/emr/patient"; import Autocomplete from "../ui/autocomplete"; -import InputWithError from "../ui/input-with-error"; interface PatientRegistrationPageProps { facilityId: string; @@ -316,71 +314,93 @@ export default function PatientRegistration(
{t("general_info_detail")}

- - - + + +
+ {errors["name"] && + errors["name"].map((error, i) => ( +
+ {error} +
+ ))} +

- - { - if (e.target.value.length > 13) return; - setForm((f) => ({ - ...f, - phone_number: e.target.value, - emergency_phone_number: samePhoneNumber - ? e.target.value - : f.emergency_phone_number, - })); + + { + if (e.target.value.length > 13) return; + setForm((f) => ({ + ...f, + phone_number: e.target.value, + emergency_phone_number: samePhoneNumber + ? e.target.value + : f.emergency_phone_number, + })); + }} + /> +
+ {errors["phone_number"] && + errors["phone_number"]?.map((error, i) => ( +
+ {error} +
+ ))} +
+ +
+ { + const newValue = !samePhoneNumber; + setSamePhoneNumber(newValue); + if (newValue) { + setForm((f) => ({ + ...f, + emergency_phone_number: f.phone_number, + })); + } }} + id="same-phone-number" /> - -
- - { - const newValue = !samePhoneNumber; - setSamePhoneNumber(newValue); - if (newValue) { - setForm((f) => ({ - ...f, - emergency_phone_number: f.phone_number, - })); - } - }} - id="same-phone-number" - /> - - +

- - { - if (e.target.value.length > 13) return; - setForm((f) => ({ - ...f, - emergency_phone_number: e.target.value, - })); - }} - disabled={samePhoneNumber} - /> - + + + { + if (e.target.value.length > 13) return; + setForm((f) => ({ + ...f, + emergency_phone_number: e.target.value, + })); + }} + disabled={samePhoneNumber} + /> +
+ {errors["emergency_phone_number"] && + errors["emergency_phone_number"]?.map((error, i) => ( +
+ {error} +
+ ))} +
{/*
*/}
- - - setForm((f) => ({ ...f, gender: value })) - } - className="flex items-center gap-4" - > - {GENDER_TYPES.map((g) => ( - - - - + + + + setForm((f) => ({ ...f, gender: value })) + } + className="flex items-center gap-4" + > + {GENDER_TYPES.map((g) => ( + + + + + ))} + +
+ {errors["gender"] && + errors["gender"]?.map((error, i) => ( +
+ {error} +
))} - - +
+
- - - + + + +
+ {errors["blood_group"] && + errors["blood_group"]?.map((error, i) => ( +
+ {error} +
+ ))} +
+
- - - setForm((f) => ({ - ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${e.target.value}`, - })) - } - /> - + + + setForm((f) => ({ + ...f, + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${e.target.value}`, + })) + } + />
- - - setForm((f) => ({ - ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${e.target.value}-${form.date_of_birth?.split("-")[2] || ""}`, - })) - } - /> - + + + setForm((f) => ({ + ...f, + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${e.target.value}-${form.date_of_birth?.split("-")[2] || ""}`, + })) + } + />
- - - setForm((f) => ({ - ...f, - date_of_birth: `${e.target.value}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, - })) - } - /> - + + + setForm((f) => ({ + ...f, + date_of_birth: `${e.target.value}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, + })) + } + />
{errors["date_of_birth"] && ( - +
+ {errors["date_of_birth"].map((error, i) => ( +
+ {error} +
+ ))} +
)} @@ -512,25 +564,32 @@ export default function PatientRegistration( {t("age_input_warning_bold")}
- - - setForm((f) => ({ - ...f, - age: e.target.value, - year_of_birth: e.target.value - ? new Date().getFullYear() - Number(e.target.value) - : undefined, - })) - } - type="number" - /> - + + + setForm((f) => ({ + ...f, + age: e.target.value, + year_of_birth: e.target.value + ? new Date().getFullYear() - Number(e.target.value) + : undefined, + })) + } + type="number" + /> +
+ {errors["year_of_birth"] && + errors["year_of_birth"]?.map((error, i) => ( +
+ {error} +
+ ))} +
+ {form.year_of_birth && (
{t("year_of_birth")} : {form.year_of_birth} @@ -540,68 +599,84 @@ export default function PatientRegistration(
- -