From 2c9c3c1dc93af770694eb3cd95b1827462940762 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 13 Dec 2024 02:22:30 +0530 Subject: [PATCH 01/10] search page --- public/locale/en.json | 2 + src/Routers/routes/PatientRoutes.tsx | 5 +- src/components/Common/Breadcrumbs.tsx | 2 +- .../Common/SearchByMultipleFields.tsx | 3 +- src/components/Facility/FacilityList.tsx | 1 - src/components/Patient/PatientFilter.tsx | 272 +++++++++++++++++- src/components/Patient/PatientIndex.tsx | 246 ++++++++++++++++ src/components/ui/tabs.tsx | 54 +++- src/hooks/useFilters.tsx | 2 +- 9 files changed, 578 insertions(+), 9 deletions(-) create mode 100644 src/components/Patient/PatientIndex.tsx diff --git a/public/locale/en.json b/public/locale/en.json index dbd0077a348..52b7b9ac1ff 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -311,6 +311,7 @@ "age": "Age", "all_changes_have_been_saved": "All changes have been saved", "all_details": "All Details", + "all_patients": "All Patients", "allergies": "Allergies", "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", @@ -1227,6 +1228,7 @@ "search_icd11_placeholder": "Search for ICD-11 Diagnoses", "search_investigation_placeholder": "Search Investigation & Groups", "search_patient": "Search Patient", + "search_patients": "Search Patients", "search_resource": "Search Resource", "see_attachments": "See Attachments", "select": "Select", diff --git a/src/Routers/routes/PatientRoutes.tsx b/src/Routers/routes/PatientRoutes.tsx index 31d6d99a037..a2f327711a5 100644 --- a/src/Routers/routes/PatientRoutes.tsx +++ b/src/Routers/routes/PatientRoutes.tsx @@ -2,16 +2,17 @@ import DeathReport from "@/components/DeathReport/DeathReport"; import InvestigationReports from "@/components/Facility/Investigations/Reports"; import FileUploadPage from "@/components/Patient/FileUploadPage"; import { InsuranceDetails } from "@/components/Patient/InsuranceDetails"; -import { PatientManager } from "@/components/Patient/ManagePatients"; import { patientTabs } from "@/components/Patient/PatientDetailsTab"; import { PatientHome } from "@/components/Patient/PatientHome"; +import PatientIndex from "@/components/Patient/PatientIndex"; import PatientNotes from "@/components/Patient/PatientNotes"; import { PatientRegister } from "@/components/Patient/PatientRegister"; import { AppRoutes } from "@/Routers/AppRouter"; const PatientRoutes: AppRoutes = { - "/patients": () => , + //"/patients": () => , + "/patients": () => , "/patient/:id": ({ id }) => , "/patient/:id/investigation_reports": ({ id }) => ( diff --git a/src/components/Common/Breadcrumbs.tsx b/src/components/Common/Breadcrumbs.tsx index 0435d7675d9..354d7508906 100644 --- a/src/components/Common/Breadcrumbs.tsx +++ b/src/components/Common/Breadcrumbs.tsx @@ -110,7 +110,7 @@ export default function Breadcrumbs({ diff --git a/src/components/Common/SearchByMultipleFields.tsx b/src/components/Common/SearchByMultipleFields.tsx index c709dab9b9f..8e10714acc2 100644 --- a/src/components/Common/SearchByMultipleFields.tsx +++ b/src/components/Common/SearchByMultipleFields.tsx @@ -30,7 +30,6 @@ import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormFi interface SearchOption { key: string; - label: string; type: "text" | "phone"; placeholder: string; value: string; @@ -241,7 +240,7 @@ const SearchByMultipleFields: React.FC = ({ {t(option.key)} - {option.label.charAt(0).toUpperCase()} + {option.shortcutKey} ))} diff --git a/src/components/Facility/FacilityList.tsx b/src/components/Facility/FacilityList.tsx index 164632e1601..1ac3574d7f5 100644 --- a/src/components/Facility/FacilityList.tsx +++ b/src/components/Facility/FacilityList.tsx @@ -197,7 +197,6 @@ export const FacilityList = () => { options={[ { key: "facility_district_name", - label: "Facility or District Name", type: "text" as const, placeholder: "facility_search_placeholder", value: qParams.search || "", diff --git a/src/components/Patient/PatientFilter.tsx b/src/components/Patient/PatientFilter.tsx index 4d0e49e9e62..860a5bf2632 100644 --- a/src/components/Patient/PatientFilter.tsx +++ b/src/components/Patient/PatientFilter.tsx @@ -1,7 +1,9 @@ import careConfig from "@careConfig"; import dayjs from "dayjs"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import FilterBadge from "@/CAREUI/display/FilterBadge"; import CareIcon from "@/CAREUI/icons/CareIcon"; import FiltersSlideover from "@/CAREUI/interactive/FiltersSlideover"; @@ -22,10 +24,13 @@ import { import MultiSelectMenuV2 from "@/components/Form/MultiSelectMenuV2"; import SelectMenuV2 from "@/components/Form/SelectMenuV2"; import DiagnosesFilter, { + DIAGNOSES_FILTER_LABELS, + DiagnosesFilterKey, FILTER_BY_DIAGNOSES_KEYS, } from "@/components/Patient/DiagnosesFilter"; import useAuthUser from "@/hooks/useAuthUser"; +import useFilters from "@/hooks/useFilters"; import useMergeState from "@/hooks/useMergeState"; import { @@ -34,14 +39,19 @@ import { DISCHARGE_REASONS, FACILITY_TYPES, GENDER_TYPES, + PATIENT_CATEGORIES, PATIENT_FILTER_CATEGORIES, RATION_CARD_CATEGORY, } from "@/common/constants"; +import { parseOptionId } from "@/common/utils"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; -import { dateQueryString } from "@/Utils/utils"; +import { dateQueryString, humanizeStrings } from "@/Utils/utils"; + +import { ICD11DiagnosisModel } from "../Diagnosis/types"; +import { getDiagnosesByIds } from "../Diagnosis/utils"; const getDate = (value: any) => value && dayjs(value).isValid() && dayjs(value).toDate(); @@ -782,3 +792,263 @@ export default function PatientFilter(props: any) { ); } + +export function PatientFilterBadges() { + const { t } = useTranslation(); + + const { qParams, FilterBadges, updateQuery } = useFilters({ + limit: 12, + cacheBlacklist: [ + "name", + "patient_no", + "phone_number", + "emergency_phone_number", + ], + }); + + const [diagnoses, setDiagnoses] = useState([]); + + const { data: districtData } = useQuery(routes.getDistrict, { + pathParams: { + id: qParams.district, + }, + prefetch: !!Number(qParams.district), + }); + + const { data: LocalBodyData } = useQuery(routes.getLocalBody, { + pathParams: { + id: qParams.lsgBody, + }, + prefetch: !!Number(qParams.lsgBody), + }); + + const { data: facilityData } = useQuery(routes.getAnyFacility, { + pathParams: { + id: qParams.facility, + }, + prefetch: !!qParams.facility, + }); + const { data: facilityAssetLocationData } = useQuery( + routes.getFacilityAssetLocation, + { + pathParams: { + facility_external_id: qParams.facility, + external_id: qParams.last_consultation_current_bed__location, + }, + prefetch: !!qParams.last_consultation_current_bed__location, + }, + ); + + const LastAdmittedToTypeBadges = () => { + const badge = (key: string, value: string | undefined, id: string) => { + return ( + value && ( + { + const lcat = qParams.last_consultation_admitted_bed_type_list + .split(",") + .filter((x: string) => x != id) + .join(","); + updateQuery({ + ...qParams, + last_consultation_admitted_bed_type_list: lcat, + }); + }} + /> + ) + ); + }; + return qParams.last_consultation_admitted_bed_type_list + .split(",") + .map((id: string) => { + const text = ADMITTED_TO.find((obj) => obj.id == id)?.text; + return badge("Bed Type", text, id); + }); + }; + + const HasConsentTypesBadges = () => { + const badge = (key: string, value: string | undefined, id: string) => { + return ( + value && ( + { + const lcat = qParams.last_consultation__consent_types + .split(",") + .filter((x: string) => x != id) + .join(","); + updateQuery({ + ...qParams, + last_consultation__consent_types: lcat, + }); + }} + /> + ) + ); + }; + + return qParams.last_consultation__consent_types + .split(",") + .map((id: string) => { + const text = [ + ...CONSENT_TYPE_CHOICES, + { id: "None", text: "No Consents" }, + ].find((obj) => obj.id == id)?.text; + return badge("Has Consent", text, id); + }); + }; + + const getTheCategoryFromId = () => { + let category_name; + if (qParams.category) { + category_name = PATIENT_CATEGORIES.find( + (item: any) => qParams.category === item.id, + )?.text; + + return String(category_name); + } else { + return ""; + } + }; + + const getDiagnosisFilterValue = (key: DiagnosesFilterKey) => { + const ids: string[] = (qParams[key] ?? "").split(","); + return ids.map((id) => diagnoses.find((obj) => obj.id == id)?.label ?? id); + }; + + useEffect(() => { + const ids: string[] = []; + FILTER_BY_DIAGNOSES_KEYS.forEach((key) => { + ids.push(...(qParams[key] ?? "").split(",").filter(Boolean)); + }); + const existing = diagnoses.filter(({ id }) => ids.includes(id)); + const objIds = existing.map((o) => o.id); + const diagnosesToBeFetched = ids.filter((id) => !objIds.includes(id)); + getDiagnosesByIds(diagnosesToBeFetched).then((data) => { + const retrieved = data.filter(Boolean) as ICD11DiagnosisModel[]; + setDiagnoses([...existing, ...retrieved]); + }); + }, [ + qParams.diagnoses, + qParams.diagnoses_confirmed, + qParams.diagnoses_provisional, + qParams.diagnoses_unconfirmed, + qParams.diagnoses_differential, + ]); + + return ( + [ + phoneNumber("Primary number", "phone_number"), + phoneNumber("Emergency number", "emergency_phone_number"), + badge("Patient name", "name"), + badge("IP/OP number", "patient_no"), + ...dateRange("Modified", "modified_date"), + ...dateRange("Created", "created_date"), + ...dateRange("Admitted", "last_consultation_encounter_date"), + ...dateRange("Discharged", "last_consultation_discharge_date"), + // Admitted to type badges + badge("No. of vaccination doses", "number_of_doses"), + kasp(), + badge("COWIN ID", "covin_id"), + badge("Is Antenatal", "is_antenatal"), + badge("Review Missed", "review_missed"), + badge("Is Medico-Legal Case", "last_consultation_medico_legal_case"), + value( + "Ration Card Category", + "ration_card_category", + qParams.ration_card_category + ? t(`ration_card__${qParams.ration_card_category}`) + : "", + ), + value( + "Facility", + "facility", + qParams.facility ? facilityData?.name || "" : "", + ), + value( + "Location", + "last_consultation_current_bed__location", + qParams.last_consultation_current_bed__location + ? facilityAssetLocationData?.name || + qParams.last_consultation_current_bed__locations + : "", + ), + badge("Facility Type", "facility_type"), + value( + "District", + "district", + qParams.district ? districtData?.name || "" : "", + ), + ordering(), + value("Category", "category", getTheCategoryFromId()), + value( + "Respiratory Support", + "ventilator_interface", + qParams.ventilator_interface && + t(`RESPIRATORY_SUPPORT_SHORT__${qParams.ventilator_interface}`), + ), + value( + "Gender", + "gender", + parseOptionId(GENDER_TYPES, qParams.gender) || "", + ), + { + name: "Admitted to", + value: ADMITTED_TO[qParams.last_consultation_admitted_to], + paramKey: "last_consultation_admitted_to", + }, + ...range("Age", "age"), + { + name: "LSG Body", + value: qParams.lsgBody ? LocalBodyData?.name || "" : "", + paramKey: "lsgBody", + }, + ...FILTER_BY_DIAGNOSES_KEYS.map((key) => + value( + DIAGNOSES_FILTER_LABELS[key], + key, + humanizeStrings(getDiagnosisFilterValue(key)), + ), + ), + badge("Declared Status", "is_declared_positive"), + ...dateRange("Declared positive", "date_declared_positive"), + ...dateRange("Last vaccinated", "last_vaccinated_date"), + { + name: "Telemedicine", + paramKey: "last_consultation_is_telemedicine", + }, + value( + "Discharge Reason", + "last_consultation__new_discharge_reason", + parseOptionId( + DISCHARGE_REASONS, + qParams.last_consultation__new_discharge_reason, + ) || "", + ), + ]} + children={ + (qParams.last_consultation_admitted_bed_type_list || + qParams.last_consultation__consent_types) && ( + <> + {qParams.last_consultation_admitted_bed_type_list && + LastAdmittedToTypeBadges()} + {qParams.last_consultation__consent_types && + HasConsentTypesBadges()} + + ) + } + /> + ); +} diff --git a/src/components/Patient/PatientIndex.tsx b/src/components/Patient/PatientIndex.tsx new file mode 100644 index 00000000000..ebf33f6688f --- /dev/null +++ b/src/components/Patient/PatientIndex.tsx @@ -0,0 +1,246 @@ +import dayjs from "dayjs"; +import { Link } from "raviger"; +import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; +import { KeyboardShortcutKey } from "@/CAREUI/interactive/KeyboardShortcut"; + +import useFilters from "@/hooks/useFilters"; + +import { GENDER_TYPES } from "@/common/constants"; + +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; +import { formatPatientAge, parsePhoneNumber } from "@/Utils/utils"; + +import Page from "../Common/Page"; +import SearchByMultipleFields from "../Common/SearchByMultipleFields"; +import { Button } from "../ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "../ui/table"; +import { TabbedSections } from "../ui/tabs"; +import PatientFilter, { PatientFilterBadges } from "./PatientFilter"; + +export default function PatientIndex() { + const { t } = useTranslation(); + + const { + qParams, + updateQuery, + advancedFilter, + Pagination, + resultsPerPage, + clearSearch, + } = useFilters({ + limit: 12, + cacheBlacklist: [ + "name", + "patient_no", + "phone_number", + "emergency_phone_number", + ], + }); + + const searchOptions = [ + { + key: "name", + type: "text" as const, + placeholder: "search_by_patient_name", + value: qParams.name || "", + shortcutKey: "n", + }, + { + key: "patient_no", + type: "text" as const, + placeholder: "search_by_patient_no", + value: qParams.patient_no || "", + shortcutKey: "u", + }, + { + key: "phone_number", + type: "phone" as const, + placeholder: "Search_by_phone_number", + value: qParams.phone_number || "", + shortcutKey: "p", + }, + + { + key: "emergency_contact_number", + type: "phone" as const, + placeholder: "search_by_emergency_phone_number", + value: qParams.emergency_phone_number || "", + shortcutKey: "e", + }, + ]; + + const handleSearch = useCallback( + (key: string, value: string) => { + const updatedQuery = { + phone_number: + key === "phone_number" + ? value.length >= 13 || value === "" + ? value + : undefined + : undefined, + name: key === "name" ? value : undefined, + patient_no: key === "patient_no" ? value : undefined, + emergency_phone_number: + key === "emergency_contact_number" + ? value.length >= 13 || value === "" + ? value + : undefined + : undefined, + }; + + updateQuery(updatedQuery); + }, + [updateQuery], + ); + + const getCleanedParams = ( + params: Record, + ) => { + const cleaned: typeof params = {}; + Object.keys(params).forEach((key) => { + if (params[key] !== 0 && params[key] !== "") { + cleaned[key] = params[key]; + } + }); + return cleaned; + }; + + const params = getCleanedParams({ + ...qParams, + page: qParams.page || 1, + limit: resultsPerPage, + is_active: + !qParams.last_consultation__new_discharge_reason && + (qParams.is_active || "True"), + phone_number: qParams.phone_number + ? parsePhoneNumber(qParams.phone_number) + : undefined, + emergency_phone_number: qParams.emergency_phone_number + ? parsePhoneNumber(qParams.emergency_phone_number) + : undefined, + local_body: qParams.lsgBody || undefined, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + last_menstruation_start_date_after: + (qParams.is_antenatal === "true" && + dayjs().subtract(9, "month").format("YYYY-MM-DD")) || + undefined, + }); + + const isValidSearch = searchOptions.some((o) => !!o.value); + + const listingQuery = useQuery(routes.patientList, { + query: params, + prefetch: isValidSearch, + }); + + return ( + + +
+
+
+ +
+ +
+ +
+ {isValidSearch && !listingQuery.data?.results.length && ( +
+ {t("no_records_found")} +
+ )} + {isValidSearch && !!listingQuery.data?.results.length && ( + + + + Name/IP/OP + Primary Ph. No. + DOB + Sex + + + + {listingQuery.data?.results.map((patient, i) => ( + + + + {patient.name} + +
+ {patient.last_consultation?.patient_no} +
+ + {patient.phone_number} + + + {patient.date_of_birth} ({formatPatientAge(patient)} + ) + + + { + GENDER_TYPES.find((g) => g.id === patient.gender) + ?.text + } + +
+ ))} +
+
+ )} + {listingQuery.data && ( + + )} +
+ +
+ + ), + }, + { label: t("all_patients"), value: "all", section: <>todo }, + ]} + /> + +
+ ); +} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index d335de80adf..bee73ee7271 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -50,4 +50,56 @@ const TabsContent = React.forwardRef< )); TabsContent.displayName = TabsPrimitive.Content.displayName; -export { Tabs, TabsList, TabsTrigger, TabsContent }; +interface SectionTabsProps { + activeTab: string; + onChange: (newTab: string) => void; + tabs: { label: string; value: string }[]; +} + +const SectionTabs = (props: SectionTabsProps) => ( + + + {props.tabs.map(({ label, value }) => ( + + {label} + + ))} + + +); + +const TabbedSections = (props: { + tabs: { label: string; value: string; section: React.ReactNode }[]; +}) => { + const [activeTab, setActiveTab] = React.useState(props.tabs[0].value); + + return ( + <> + setActiveTab(t)} + /> +
+ {props.tabs.map(({ value, section }) => ( + + ))} +
+ + ); +}; + +export { + Tabs, + TabsList, + TabsTrigger, + TabsContent, + SectionTabs, + TabbedSections, +}; diff --git a/src/hooks/useFilters.tsx b/src/hooks/useFilters.tsx index 63ffa58822e..8486a4dc622 100644 --- a/src/hooks/useFilters.tsx +++ b/src/hooks/useFilters.tsx @@ -209,7 +209,7 @@ export default function useFilters({ return (
{compiledBadges.map((props) => ( From 64168bc496ec1647c69a8e9c02935b18144e973e Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Sat, 14 Dec 2024 02:18:10 +0530 Subject: [PATCH 02/10] midway --- package-lock.json | 111 ++++++++++++++++++ package.json | 5 +- public/locale/en.json | 5 + src/CAREUI/misc/SectionNavigator.tsx | 78 ++++++++++++ src/Routers/routes/PatientRoutes.tsx | 8 +- src/components/Common/Page.tsx | 2 +- src/components/Patient/PatientIndex.tsx | 110 ++++++++++++++--- .../Patient/PatientRegistration.tsx | 98 ++++++++++++++++ src/components/Patient/Utils.ts | 18 +++ src/components/ui/checkbox.tsx | 42 +++++++ src/components/ui/input.tsx | 35 ++++-- 11 files changed, 483 insertions(+), 29 deletions(-) create mode 100644 src/CAREUI/misc/SectionNavigator.tsx create mode 100644 src/components/Patient/PatientRegistration.tsx create mode 100644 src/components/ui/checkbox.tsx diff --git a/package-lock.json b/package-lock.json index 55d80c284cf..97da48a3f8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", @@ -4545,6 +4546,116 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz", + "integrity": "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", diff --git a/package.json b/package.json index a1f78be50e9..ca35e3b9d12 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", @@ -148,13 +149,13 @@ "snyk": "^1.1294.0", "tailwindcss": "^3.4.14", "ts-node": "^10.9.2", + "tsx": "^4.19.2", "typescript": "^5.6.3", "uuid": "^11.0.2", "vite": "^5.4.10", "vite-plugin-checker": "^0.8.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-static-copy": "^2.0.0", - "tsx": "^4.19.2", "zod": "^3.23.8" }, "optionalDependencies": { @@ -187,4 +188,4 @@ "node": ">=22.11.0" }, "packageManager": "npm@10.9.0" -} \ No newline at end of file +} diff --git a/public/locale/en.json b/public/locale/en.json index 52b7b9ac1ff..bab434593de 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -658,8 +658,10 @@ "emergency_contact": "Emergency Contact", "emergency_contact_number": "Emergency Contact Number", "emergency_contact_person_name": "Emergency Contact Person Name", + "emergency_contact_person_name_details": "Emergency contact person (Father, Mother, Spouse, Sibling, Friend)", "emergency_contact_person_name_volunteer": "Emergency Contact Person Name (Volunteer)", "emergency_contact_volunteer": "Emergency Contact (Volunteer)", + "emergency_phone_number": "Emergency Phone Number", "empty_date_time": "--:-- --; --/--/----", "encounter_date_field_label__A": "Date & Time of Admission to the Facility", "encounter_date_field_label__DC": "Date & Time of Domiciliary Care commencement", @@ -744,6 +746,7 @@ "full_name": "Full Name", "full_screen": "Full Screen", "gender": "Gender", + "general_info_detail": "Provide the patient's personal details, including name, date of birth, gender, and contact information for accurate identification and communication.", "generate_link_abha": "Generate/Link ABHA Number", "generate_report": "Generate Report", "generated_summary_caution": "This is a computer generated summary using the information captured in the CARE system.", @@ -1350,6 +1353,7 @@ "type_b_cylinders": "B Type Cylinders", "type_c_cylinders": "C Type Cylinders", "type_d_cylinders": "D Type Cylinders", + "type_patient_name": "Type Patient Name", "type_to_search": "Type to search", "type_your_comment": "Type your comment", "type_your_reason_here": "Type your reason here", @@ -1394,6 +1398,7 @@ "upload_report": "Upload Report", "uploading": "Uploading", "use_existing_abha_address": "Use Existing ABHA Address", + "use_phone_number_for_emergency": "Use this phone number for emergency contact", "user_deleted_successfuly": "User Deleted Successfuly", "user_management": "User Management", "username": "Username", diff --git a/src/CAREUI/misc/SectionNavigator.tsx b/src/CAREUI/misc/SectionNavigator.tsx new file mode 100644 index 00000000000..78027bde371 --- /dev/null +++ b/src/CAREUI/misc/SectionNavigator.tsx @@ -0,0 +1,78 @@ +import { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; + +import { Button } from "@/components/ui/button"; + +export default function SectionNavigator(props: { + sections: { label: string; id: string }[]; + className?: string; +}) { + const { sections, className } = props; + + const [activeSection, setActiveSection] = useState(null); + + useEffect(() => { + const updateActiveSection = () => { + sections.forEach((section) => { + const element = document.getElementById(section.id); + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top >= 0 && rect.bottom <= window.innerHeight) { + setActiveSection(section.id); + } + } + }); + }; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveSection(entry.target.id); + } + }); + }, + { rootMargin: "0px 0px -80% 0px", threshold: 0.1 }, + ); + + sections.forEach((section) => { + const element = document.getElementById(section.id); + if (element) { + observer.observe(element); + } + }); + + updateActiveSection(); // Update on page load + + return () => { + sections.forEach((section) => { + const element = document.getElementById(section.id); + if (element) { + observer.unobserve(element); + } + }); + }; + }, [sections]); + + return ( +
+ {sections.map((section) => ( + + ))} +
+ ); +} diff --git a/src/Routers/routes/PatientRoutes.tsx b/src/Routers/routes/PatientRoutes.tsx index a2f327711a5..6d1ee4beb13 100644 --- a/src/Routers/routes/PatientRoutes.tsx +++ b/src/Routers/routes/PatientRoutes.tsx @@ -7,6 +7,7 @@ import { PatientHome } from "@/components/Patient/PatientHome"; import PatientIndex from "@/components/Patient/PatientIndex"; import PatientNotes from "@/components/Patient/PatientNotes"; import { PatientRegister } from "@/components/Patient/PatientRegister"; +import PatientRegistration from "@/components/Patient/PatientRegistration"; import { AppRoutes } from "@/Routers/AppRouter"; @@ -17,9 +18,12 @@ const PatientRoutes: AppRoutes = { "/patient/:id/investigation_reports": ({ id }) => ( ), - "/facility/:facilityId/patient": ({ facilityId }) => ( - + "/facility/:facilityId/register-patient": ({ facilityId }) => ( + ), + // "/facility/:facilityId/patient": ({ facilityId }) => ( + // + // ), "/facility/:facilityId/patient/:id": ({ facilityId, id }) => ( ), diff --git a/src/components/Common/Page.tsx b/src/components/Common/Page.tsx index 32e763c45b9..558d1432182 100644 --- a/src/components/Common/Page.tsx +++ b/src/components/Common/Page.tsx @@ -33,7 +33,7 @@ export default function Page(props: PageProps) { let padding = ""; if (!props.noImplicitPadding) { - if (!props.hideBack || props.componentRight) padding = "py-3 md:p-6"; + if (!props.hideBack || props.componentRight) padding = "py-3 md:px-6"; else padding = "px-6 py-5"; } diff --git a/src/components/Patient/PatientIndex.tsx b/src/components/Patient/PatientIndex.tsx index ebf33f6688f..87b9172c85f 100644 --- a/src/components/Patient/PatientIndex.tsx +++ b/src/components/Patient/PatientIndex.tsx @@ -1,11 +1,12 @@ import dayjs from "dayjs"; -import { Link } from "raviger"; -import { useCallback } from "react"; +import { navigate } from "raviger"; +import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { KeyboardShortcutKey } from "@/CAREUI/interactive/KeyboardShortcut"; +import useAuthUser from "@/hooks/useAuthUser"; import useFilters from "@/hooks/useFilters"; import { GENDER_TYPES } from "@/common/constants"; @@ -14,8 +15,11 @@ import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; import { formatPatientAge, parsePhoneNumber } from "@/Utils/utils"; +import * as Notification from "../../Utils/Notifications"; import Page from "../Common/Page"; import SearchByMultipleFields from "../Common/SearchByMultipleFields"; +import FacilitiesSelectDialogue from "../ExternalResult/FacilitiesSelectDialogue"; +import { FacilityModel } from "../Facility/models"; import { Button } from "../ui/button"; import { Table, @@ -27,10 +31,14 @@ import { } from "../ui/table"; import { TabbedSections } from "../ui/tabs"; import PatientFilter, { PatientFilterBadges } from "./PatientFilter"; +import { getPatientUrl } from "./Utils"; export default function PatientIndex() { const { t } = useTranslation(); - + const [showDialog, setShowDialog] = useState<"create" | "list-discharged">(); + const [selectedFacility, setSelectedFacility] = useState({ + name: "", + }); const { qParams, updateQuery, @@ -80,6 +88,8 @@ export default function PatientIndex() { }, ]; + const authUser = useAuthUser(); + const handleSearch = useCallback( (key: string, value: string) => { const updatedQuery = { @@ -144,6 +154,16 @@ export default function PatientIndex() { prefetch: isValidSearch, }); + const { data: permittedFacilities } = useQuery( + routes.getPermittedFacilities, + { + query: { limit: 1 }, + }, + ); + + const onlyAccessibleFacility = + permittedFacilities?.count === 1 ? permittedFacilities.results[0] : null; + return ( +
-
+
@@ -192,14 +212,14 @@ export default function PatientIndex() { {listingQuery.data?.results.map((patient, i) => ( - + navigate(getPatientUrl(patient))} + > - - {patient.name} - + {patient.name} +
{patient.last_consultation?.patient_no}
@@ -224,8 +244,51 @@ export default function PatientIndex() { {listingQuery.data && ( )} -
-
); }, ); From a320c84fe8041f733d65c8fbe47bfaa59a276078 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Sat, 14 Dec 2024 05:30:49 +0530 Subject: [PATCH 03/10] social profile --- package-lock.json | 168 +++++++ package.json | 7 +- public/locale/en.json | 8 +- .../Patient/PatientRegistration.tsx | 418 +++++++++++++++++- src/components/ui/radio-group.tsx | 54 +++ src/components/ui/textarea.tsx | 32 +- 6 files changed, 672 insertions(+), 15 deletions(-) create mode 100644 src/components/ui/radio-group.tsx diff --git a/package-lock.json b/package-lock.json index 0d5be2dc9d1..c74005f5969 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.1", @@ -5468,6 +5469,173 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.2.tgz", + "integrity": "sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", + "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", diff --git a/package.json b/package.json index e6385f36772..84f3c2f97ab 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.1", @@ -160,10 +161,10 @@ "zod": "^3.23.8" }, "optionalDependencies": { - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", "@esbuild/linux-arm64": "latest", - "@esbuild/linux-x64": "latest" + "@esbuild/linux-x64": "latest", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0" }, "browserslist": { "production": [ diff --git a/public/locale/en.json b/public/locale/en.json index 17f48673094..1d1c61de3dc 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1589,5 +1589,11 @@ "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", - "zoom_out": "Zoom Out" + "zoom_out": "Zoom Out", + "day": "Day", + "month": "Month", + "year": "Year", + "use_address_as_permanent": "Use this address for permanent address", + "landmark": "Landmark", + "social_profile_detail": "Include occupation, ration card category, socioeconomic status, and 
domestic healthcare support for a complete profile." } diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 046edf89222..bd580daed5c 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -1,11 +1,41 @@ -import { useState } from "react"; +import careConfig from "@careConfig"; +import { RadioGroupItem } from "@radix-ui/react-radio-group"; +import { useQuery } from "@tanstack/react-query"; +import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import SectionNavigator from "@/CAREUI/misc/SectionNavigator"; +import useDebounce from "@/hooks/useDebounce"; + +import { + GENDER_TYPES, + OCCUPATION_TYPES, + RATION_CARD_CATEGORY, +} from "@/common/constants"; +import countryList from "@/common/static/countries.json"; +import { validatePincode } from "@/common/validation"; + +import routes from "@/Utils/request/api"; +import query from "@/Utils/request/query"; +import request from "@/Utils/request/request"; +import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getPincodeDetails, includesIgnoreCase } from "@/Utils/utils"; + import Page from "../Common/Page"; import { Checkbox } from "../ui/checkbox"; import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { RadioGroup } from "../ui/radio-group"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; +import { Textarea } from "../ui/textarea"; import { PatientModel } from "./models"; interface PatientRegistrationPageProps { @@ -26,8 +56,86 @@ export default function PatientRegistration( { label: t("patient__insurance-details"), id: "insurance-details" }, ]; - const [form, setForm] = useState>({}); + const [form, setForm] = useState>({ + nationality: "India", + }); + + const statesQuery = useQuery({ + queryKey: ["states"], + queryFn: query(routes.statesList), + }); + + const districtsQuery = useQuery({ + queryKey: ["districts", form.state], + enabled: !!form.state, + queryFn: query(routes.getDistrictByState, { + pathParams: { id: form.state?.toString() || "" }, + }), + }); + + const localBodyQuery = useQuery({ + queryKey: ["localbodies", form.district], + enabled: !!form.district, + queryFn: query(routes.getLocalbodyByDistrict, { + pathParams: { id: form.district?.toString() || "" }, + }), + }); + + const wardsQuery = useQuery({ + queryKey: ["wards", form.local_body], + enabled: !!form.local_body, + queryFn: query(routes.getWardByLocalBody, { + pathParams: { id: form.local_body?.toString() || "" }, + }), + }); + const [samePhoneNumber, setSamePhoneNumber] = useState(false); + const [sameAddress, setSameAddress] = useState(false); + const [ageDob, setAgeDob] = useState<"dob" | "age">("dob"); + const [showAutoFilledPincode, setShowAutoFilledPincode] = useState(false); + + const handlePincodeChange = async (value: string) => { + if (!validatePincode(value)) return; + + const pincodeDetails = await getPincodeDetails( + value, + careConfig.govDataApiKey, + ); + if (!pincodeDetails) return; + + const matchedState = statesQuery.data?.results?.find((state) => { + return includesIgnoreCase(state.name, pincodeDetails.statename); + }); + if (!matchedState) return; + + const fetchedDistricts = await request(routes.getDistrictByState, { + pathParams: { id: matchedState.id?.toString() || "" }, + }); + if (!fetchedDistricts.data) return; + + const matchedDistrict = fetchedDistricts.data.find((district) => { + return includesIgnoreCase(district.name, pincodeDetails.districtname); + }); + if (!matchedDistrict) return; + + setForm((f) => ({ + ...f, + state: matchedState.id, + district: matchedDistrict.id, + })); + setShowAutoFilledPincode(true); + setTimeout(() => { + setShowAutoFilledPincode(false); + }, 2000); + }; + + useEffect(() => { + const timeout = setTimeout( + () => handlePincodeChange(form.pincode?.toString() || ""), + 1000, + ); + return () => clearTimeout(timeout); + }, [form.pincode]); const title = !patientId ? t("add_details_of_patient") @@ -90,6 +198,312 @@ export default function PatientRegistration( label={t("emergency_contact_person_name_details")} placeholder={t("emergency_contact_person_name")} /> +
+ + setForm((f) => ({ ...f, gender: Number(value) })) + } + className="flex items-center gap-4" + > + {GENDER_TYPES.map((g) => ( + <> + + + + ))} + +
+ + setAgeDob(value as typeof ageDob) + } + > + + {[ + ["dob", t("date_of_birth")], + ["age", t("age")], + ].map(([key, label]) => ( + {label} + ))} + + +
+ + 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: `${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] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${e.target.value}`, + })) + } + /> +
+
+ + + +
+
+