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/cypress/e2e/patient_spec/patient_search.cy.ts b/cypress/e2e/patient_spec/patient_search.cy.ts index 96ba5d94a47..b63e456be9b 100644 --- a/cypress/e2e/patient_spec/patient_search.cy.ts +++ b/cypress/e2e/patient_spec/patient_search.cy.ts @@ -17,7 +17,7 @@ describe("Patient Search", () => { it("search patient with phone number and verifies details", () => { patientSearch - .selectFacility("PHC Kakkanad -1") + .selectFacility("Arike") .clickSearchPatients() .searchPatient(TEST_PHONE) .verifySearchResults(PATIENT_DETAILS); diff --git a/package-lock.json b/package-lock.json index edfd7250ef1..a95d10cbac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -146,8 +146,8 @@ "node": ">=22.8.0" }, "optionalDependencies": { - "@esbuild/linux-arm64": "*", - "@esbuild/linux-x64": "*", + "@esbuild/linux-arm64": "latest", + "@esbuild/linux-x64": "latest", "@rollup/rollup-linux-arm64-gnu": "4.29.1", "@rollup/rollup-linux-x64-gnu": "4.29.1" } @@ -3720,7 +3720,6 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz", "integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==", - "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.6", "css-box-model": "^1.2.1", @@ -5379,7 +5378,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz", "integrity": "sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", diff --git a/public/locale/en.json b/public/locale/en.json index 4e587ac6a4d..ff3c2bd871a 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", @@ -651,6 +652,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", @@ -1633,6 +1638,7 @@ "select": "Select", "select_all": "Select All", "select_date": "Select date", + "select_department": "Select Department", "select_diff_role": "Please select a different role", "select_eligible_policy": "Select an Eligible Insurance Policy", "select_facility": "Select Facility", 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 e7705b1b797..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 { CarePatientTokenKey } 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(CarePatientTokenKey) || "{}", -); - 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/Routers/routes/questionnaireRoutes.tsx b/src/Routers/routes/questionnaireRoutes.tsx index 69d3a114d5a..044f9fcc1d4 100644 --- a/src/Routers/routes/questionnaireRoutes.tsx +++ b/src/Routers/routes/questionnaireRoutes.tsx @@ -1,4 +1,5 @@ import { QuestionnaireList } from "@/components/Questionnaire"; +import QuestionnaireEditor from "@/components/Questionnaire/QuestionnaireEditor"; import { QuestionnaireShow } from "@/components/Questionnaire/show"; import { AppRoutes } from "../AppRouter"; @@ -6,6 +7,7 @@ import { AppRoutes } from "../AppRouter"; const QuestionnaireRoutes: AppRoutes = { "/questionnaire": () => , "/questionnaire/:id": ({ id }) => , + "/questionnaire/:id/edit": ({ id }) => , }; export default QuestionnaireRoutes; diff --git a/src/common/constants.tsx b/src/common/constants.tsx index 39fe769520c..64af8d373ac 100644 --- a/src/common/constants.tsx +++ b/src/common/constants.tsx @@ -1757,8 +1757,3 @@ export const HEADER_CONTENT_TYPES = { } as const; export const ADMIN_USER_TYPES = ["DistrictAdmin", "StateAdmin"] as const; - -/** - * @deprecated use `LocalStorageKeys.patientTokenKey` instead - */ -export const CarePatientTokenKey = LocalStorageKeys.patientTokenKey; 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/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx index 11f9ea4529b..3cbad3a75c6 100644 --- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx +++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx @@ -1,10 +1,8 @@ -import { useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import PaginatedList from "@/CAREUI/misc/PaginatedList"; -import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import routes from "@/Utils/request/api"; @@ -13,8 +11,6 @@ import { Encounter } from "@/types/emr/encounter"; import { Question } from "@/types/questionnaire/question"; import { QuestionnaireResponse } from "@/types/questionnaire/questionnaireResponse"; -import { StructuredResponseView } from "./StructuredResponseView"; - interface Props { encounter: Encounter; } @@ -128,21 +124,6 @@ function QuestionGroup({ export default function QuestionnaireResponsesList({ encounter }: Props) { const { t } = useTranslation(); - const [expandedResponseIds, setExpandedResponseIds] = useState>( - new Set(), - ); - - const toggleResponse = (id: string) => { - setExpandedResponseIds((prev) => { - const next = new Set(prev); - if (next.has(id)) { - next.delete(id); - } else { - next.add(id); - } - return next; - }); - }; return ( {formatDateTime(item.created_date)} - by {item.created_by?.first_name || ""}{" "} - {item.created_by?.last_name || ""} - {` (${item.created_by?.user_type})`} + {!item.questionnaire && ( + <> + {Object.values( + item.structured_responses ?? {}, + )[0]?.submit_type === "CREATE" + ? "Created" + : "Updated"}{" "} + + )} + { + <> + by {item.created_by?.first_name || ""}{" "} + {item.created_by?.last_name || ""} + {item.created_by?.user_type && + ` (${item.created_by?.user_type})`} + + } - - {expandedResponseIds.has(item.id) && ( + {item.questionnaire && (
- {item.questionnaire ? ( - // Existing questionnaire response rendering -
- {item.questionnaire?.questions.map( - (question: Question) => { - // Skip structured questions for now as they need special handling - if (question.type === "structured") return null; - - const response = item.responses.find( - (r) => r.question_id === question.id, - ); +
+ {item.questionnaire?.questions.map( + (question: Question) => { + // Skip structured questions for now as they need special handling + if (question.type === "structured") return null; - if (question.type === "group") { - return ( - - ); - } - - if (!response) return null; + const response = item.responses.find( + (r) => r.question_id === question.id, + ); + if (question.type === "group") { return ( - ); - }, - )} -
- ) : item.structured_responses ? ( - // New structured response rendering - Object.entries(item.structured_responses).map( - ([type, response]) => { + } + + if (!response) return null; + return ( - ); }, - ) - ) : null} + )} +
)} diff --git a/src/components/Facility/ConsultationDetails/StructuredResponseView.tsx b/src/components/Facility/ConsultationDetails/StructuredResponseView.tsx deleted file mode 100644 index 9d6b06ae2b8..00000000000 --- a/src/components/Facility/ConsultationDetails/StructuredResponseView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { AllergyTable } from "@/components/Patient/allergy/AllergyTable"; -import { DiagnosisTable } from "@/components/Patient/diagnosis/DiagnosisTable"; -import { SymptomTable } from "@/components/Patient/symptoms/SymptomTable"; - -import query from "@/Utils/request/query"; -import { AllergyIntolerance } from "@/types/emr/allergyIntolerance/allergyIntolerance"; -import allergyApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; -import { Diagnosis } from "@/types/emr/diagnosis/diagnosis"; -import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi"; -import { Symptom } from "@/types/emr/symptom/symptom"; -import symptomApi from "@/types/emr/symptom/symptomApi"; - -interface Props { - type: string; - id: string; - patientId: string; - encounterId: string; -} - -export function StructuredResponseView({ - type, - id, - patientId, - encounterId, -}: Props) { - const getRouteAndParams = () => { - const params: Record = { patientId }; - switch (type) { - case "symptom": - return { - route: symptomApi.retrieveSymptom, - pathParams: { ...params, symptomId: id }, - queryParams: { encounter: encounterId }, - }; - case "diagnosis": - return { - route: diagnosisApi.retrieveDiagnosis, - pathParams: { ...params, diagnosisId: id }, - queryParams: { encounter: encounterId }, - }; - case "allergy_intolerance": - return { - route: allergyApi.retrieveAllergy, - pathParams: { ...params, allergyId: id }, - queryParams: { encounter: encounterId }, - }; - } - }; - - const routeConfig = getRouteAndParams(); - - const { data, isLoading, error } = useQuery({ - queryKey: [type, id], - queryFn: query(routeConfig?.route as any, { - pathParams: routeConfig?.pathParams, - queryParams: routeConfig?.queryParams, - }), - enabled: !!id && !!routeConfig, - }); - - if (!routeConfig) return null; - - if (isLoading) { - return
; - } - - if (error) { - console.error(`Error loading ${type}:`, error); - return
Error loading {type}
; - } - - switch (type) { - case "symptom": - return ( - - ); - case "diagnosis": - return ( - - ); - case "allergy_intolerance": - return ( - - ); - default: - return null; - } -} 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(
- -