From 7f75a180b0c3a20d30a0e35ab1b82acf4588d910 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 14 Nov 2024 11:51:31 +0000 Subject: [PATCH 01/40] removed lodash imports and dependencies and wrote js equivalents --- package-lock.json | 25 ------- package.json | 2 - src/Utils/Notifications.js | 3 +- src/Utils/stringUtils.ts | 22 ++++++ .../Common/ExcelFIleDragAndDrop.tsx | 7 +- .../Facility/Investigations/Reports/index.tsx | 21 +++--- .../Facility/Investigations/Reports/utils.tsx | 68 +++++++++++++------ .../Investigations/ShowInvestigation.tsx | 33 +++++---- .../Facility/Investigations/Table.tsx | 10 ++- src/components/Form/AutoCompleteAsync.tsx | 20 +++--- src/components/Form/Form.tsx | 9 ++- src/components/Patient/DiagnosesFilter.tsx | 28 +++++++- src/components/Patient/PatientRegister.tsx | 20 ++++-- src/components/Patient/SampleDetails.tsx | 6 +- src/components/Patient/SampleTestCard.tsx | 6 +- 15 files changed, 178 insertions(+), 102 deletions(-) create mode 100644 src/Utils/stringUtils.ts diff --git a/package-lock.json b/package-lock.json index f5edb998e4e..3c4f6e7758e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,6 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", - "lodash-es": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", @@ -68,7 +67,6 @@ "@types/events": "^3.0.3", "@types/google.maps": "^3.58.1", "@types/jsdom": "^21.1.7", - "@types/lodash-es": "^4.17.12", "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", @@ -5176,23 +5174,6 @@ "optional": true, "peer": true }, - "node_modules/@types/lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-jzqWo/uQP/iqeGGTjhgFp2yaCrCYTauASQcpdzESNCkHjSprBJVcZP9KG9aQ0q+xcsXiKd/iuw/4dLjS3Odc7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -11938,12 +11919,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", diff --git a/package.json b/package.json index 60250c2209e..bea0a664f1d 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", - "lodash-es": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", @@ -107,7 +106,6 @@ "@types/events": "^3.0.3", "@types/google.maps": "^3.58.1", "@types/jsdom": "^21.1.7", - "@types/lodash-es": "^4.17.12", "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 5b3ecdf143c..6eb0adaea5a 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,6 +1,7 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { camelCase, startCase } from "lodash-es"; + +import { camelCase, startCase } from "./stringUtils"; defaultModules.set(PNotifyMobile, {}); diff --git a/src/Utils/stringUtils.ts b/src/Utils/stringUtils.ts new file mode 100644 index 00000000000..0cc2a246487 --- /dev/null +++ b/src/Utils/stringUtils.ts @@ -0,0 +1,22 @@ +// Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. +export const camelCase = (str: string) => { + return str + .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => + index === 0 ? match.toLowerCase() : match.toUpperCase(), + ) + .replace(/\s+/g, "") + .replace(/([A-Z])/g, (match) => match.toLowerCase()); +}; + +// Capitalize the first letter of each word in a string +export const startCase = (str: string) => { + return str + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); +}; + +// Capitalize the first letter of a string +export const capitalize = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +}; diff --git a/src/components/Common/ExcelFIleDragAndDrop.tsx b/src/components/Common/ExcelFIleDragAndDrop.tsx index 67c64f0f433..d868e03f2a4 100644 --- a/src/components/Common/ExcelFIleDragAndDrop.tsx +++ b/src/components/Common/ExcelFIleDragAndDrop.tsx @@ -1,4 +1,3 @@ -import { forIn } from "lodash-es"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as XLSX from "xlsx"; @@ -68,9 +67,9 @@ export default function ExcelFileDragAndDrop({ const data = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); //converts the date to string data.forEach((row: any) => { - forIn(row, (value: any, key: string) => { - if (value instanceof Date) { - row[key] = value.toISOString().split("T")[0]; + Object.keys(row).forEach((key) => { + if (row[key] instanceof Date) { + row[key] = row[key].toISOString().split("T")[0]; } }); }); diff --git a/src/components/Facility/Investigations/Reports/index.tsx b/src/components/Facility/Investigations/Reports/index.tsx index e711372da47..32f0abcb7bc 100644 --- a/src/components/Facility/Investigations/Reports/index.tsx +++ b/src/components/Facility/Investigations/Reports/index.tsx @@ -1,4 +1,3 @@ -import _ from "lodash"; import { useCallback, useReducer, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -179,16 +178,16 @@ const InvestigationReports = ({ id }: any) => { ), ); - const investigationList = _.chain(data) - .flatMap((i) => i?.data?.results) - .compact() - .flatten() - .map((i) => ({ - ...i, - name: `${i.name} ${i.groups[0].name && " | " + i.groups[0].name} `, - })) - .unionBy("external_id") - .value(); + const investigationList = Array.from( + data + .flatMap((i) => i?.data?.results || []) + .map((i) => ({ + ...i, + name: `${i.name} ${i.groups[0].name ? " | " + i.groups[0].name : ""}`, + })) + .reduce((map, item) => map.set(item.external_id, item), new Map()) + .values(), + ); dispatch({ type: "set_investigations", payload: investigationList }); dispatch({ diff --git a/src/components/Facility/Investigations/Reports/utils.tsx b/src/components/Facility/Investigations/Reports/utils.tsx index 073be541600..5a84e6fbe60 100644 --- a/src/components/Facility/Investigations/Reports/utils.tsx +++ b/src/components/Facility/Investigations/Reports/utils.tsx @@ -1,31 +1,55 @@ -import _ from "lodash"; -import { findIndex, memoize } from "lodash-es"; - import { InvestigationResponse } from "@/components/Facility/Investigations/Reports/types"; +const memoize = any>(fn: T): T => { + const cache: Record> = {}; + return ((...args: Parameters): ReturnType => { + const key = JSON.stringify(args); + if (!cache[key]) { + cache[key] = fn(...args); + } + return cache[key]; + }) as T; +}; + export const transformData = memoize((data: InvestigationResponse) => { - const sessions = _.chain(data) - .map((value: any) => { - return { - ...value.session_object, - facility_name: value.consultation_object?.facility_name, - facility_id: value.consultation_object?.facility, - }; - }) - .uniqBy("session_external_id") - .orderBy("session_created_date", "desc") - .value(); - const groupByInvestigation = _.chain(data) - .groupBy("investigation_object.external_id") - .values() - .value(); + const sessions = Array.from( + new Map( + data.map((value: any) => [ + value.session_object.session_external_id, + { + ...value.session_object, + facility_name: value.consultation_object?.facility_name, + facility_id: value.consultation_object?.facility, + }, + ]), + ).values(), + ).sort( + (a, b) => + new Date(b.session_created_date).getTime() - + new Date(a.session_created_date).getTime(), + ); + + const groupByInvestigation = Object.values( + data.reduce( + (acc, value: any) => { + const key = value.investigation_object.external_id; + if (!acc[key]) acc[key] = []; + acc[key].push(value); + return acc; + }, + {} as { [key: string]: any[] }, + ), + ); + const reqData = groupByInvestigation.map((value: any) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val: any) => { - const sessionIndex = findIndex(sessions, [ - "session_external_id", - val.session_object.session_external_id, - ]); + const sessionIndex = sessions.findIndex( + (session) => + session.session_external_id === + val.session_object.session_external_id, + ); + if (sessionIndex > -1) { sessionValues[sessionIndex] = { min: val.investigation_object.min_value, diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index 11e9b5cc0ad..daaf25f97d9 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -1,5 +1,3 @@ -import _ from "lodash"; -import { set } from "lodash-es"; import { useCallback, useReducer } from "react"; import { useTranslation } from "react-i18next"; @@ -93,7 +91,14 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - set(changedFields, name, value); + const keys = name.split("."); + let current = changedFields; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key]) current[key] = {}; + current = current[key]; + } + current[keys[keys.length - 1]] = value; dispatch({ type: "set_changed_fields", changedFields }); }; @@ -151,15 +156,19 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { }; const handleUpdateCancel = useCallback(() => { - const changedValues = _.chain(state.initialValues) - .map((val: any, _key: string) => ({ - id: val?.id, - initialValue: val?.notes || val?.value || null, - value: val?.value || null, - notes: val?.notes || null, - })) - .reduce((acc: any, cur: any) => ({ ...acc, [cur.id]: cur }), {}) - .value(); + const changedValues = Object.keys(state.initialValues).reduce( + (acc: any, key: any) => { + const val = state.initialValues[key]; + acc[key] = { + id: val?.id, + initialValue: val?.notes || val?.value || null, + value: val?.value || null, + notes: val?.notes || null, + }; + return acc; + }, + {}, + ); dispatch({ type: "set_changed_fields", changedFields: changedValues }); }, [state.initialValues]); diff --git a/src/components/Facility/Investigations/Table.tsx b/src/components/Facility/Investigations/Table.tsx index 3a267279eb7..0eccf004bde 100644 --- a/src/components/Facility/Investigations/Table.tsx +++ b/src/components/Facility/Investigations/Table.tsx @@ -1,4 +1,3 @@ -import { set } from "lodash-es"; import { useState } from "react"; import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; @@ -60,7 +59,14 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { const handleValueChange = (value: any, name: string) => { const form = { ...state }; - set(form, name, value); + const keys = name.split("."); + let current = form; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key]) current[key] = {}; + current = current[key]; + } + current[keys[keys.length - 1]] = value; dispatch({ type: "set_form", form }); }; diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index b0cb9208d73..903a3b8090d 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -5,7 +5,6 @@ import { ComboboxOption, ComboboxOptions, } from "@headlessui/react"; -import { debounce } from "lodash-es"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -69,10 +68,14 @@ const AutoCompleteAsync = (props: Props) => { const hasSelection = (!multiple && selected) || (multiple && selected?.length > 0); - const fetchDataAsync = useMemo( - () => - debounce(async (query: string) => { - setLoading(true); + const fetchDataAsync = useMemo(() => { + let timeoutId: ReturnType; + + return async (query: string) => { + clearTimeout(timeoutId); + setLoading(true); + + timeoutId = setTimeout(async () => { const data = ((await fetchData(query)) || [])?.filter((d: any) => filter ? filter(d) : true, ); @@ -82,10 +85,11 @@ const AutoCompleteAsync = (props: Props) => { } else { setData(data); } + setLoading(false); - }, debounceTime), - [fetchData, showNOptions, debounceTime], - ); + }, debounceTime); + }; + }, [fetchData, showNOptions, debounceTime]); useEffect(() => { fetchDataAsync(query); diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index e31467b6c2a..4f06a441f68 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,4 +1,3 @@ -import { isEmpty, omitBy } from "lodash-es"; import { useEffect, useMemo, useState } from "react"; import { Cancel, Submit } from "@/components/Common/ButtonV2"; @@ -58,7 +57,13 @@ const Form = ({ event.stopPropagation(); if (validate) { - const errors = omitBy(validate(state.form), isEmpty) as FormErrors; + const errors = Object.fromEntries( + Object.entries(validate(state.form)).filter( + ([_key, value]) => + value !== "" && value !== null && value !== undefined, + ), + ) as FormErrors; + if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index c8fc2bc1a44..7cc5e1fd3f3 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -1,5 +1,4 @@ -import { debounce } from "lodash-es"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; @@ -34,6 +33,25 @@ interface Props { value?: string; onChange: (event: { name: DiagnosesFilterKey; value: string }) => void; } + +function useDebounce(callback: (...args: any[]) => void, delay: number) { + const timeoutRef = useRef | null>(null); + + const debouncedCallback = useCallback( + (...args: any[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callback(...args); + }, delay); + }, + [callback, delay], + ); + + return debouncedCallback; +} + export default function DiagnosesFilter(props: Props) { const { t } = useTranslation(); const [diagnoses, setDiagnoses] = useState([]); @@ -68,6 +86,10 @@ export default function DiagnosesFilter(props: Props) { }); }, [props.value]); + const debouncedQuery = useDebounce((query: string) => { + refetch({ query: { query } }); + }, 300); + return ( obj.id)} optionLabel={(option) => option.label} optionValue={(option) => option} - onQuery={debounce((query: string) => refetch({ query: { query } }), 300)} + onQuery={debouncedQuery} isLoading={loading} /> ); diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 5cc958d44de..66f19b0b1b3 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -1,6 +1,4 @@ import careConfig from "@careConfig"; -import { startCase, toLower } from "lodash-es"; -import { debounce } from "lodash-es"; import { navigate } from "raviger"; import { useCallback, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -76,6 +74,7 @@ import { usePubSub } from "@/Utils/pubsubContext"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; +import { startCase } from "@/Utils/stringUtils"; import { compareBy, dateQueryString, @@ -627,7 +626,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { ? formData.last_vaccinated_date : null : null, - name: startCase(toLower(formData.name)), + name: startCase(formData.name.toLowerCase()), pincode: formData.pincode ? formData.pincode : undefined, gender: Number(formData.gender), nationality: formData.nationality, @@ -747,7 +746,20 @@ export const PatientRegister = (props: PatientRegisterProps) => { }); }; - const duplicateCheck = debounce(async (phoneNo: string) => { + const useDebounce = (callback: (...args: any[]) => void, delay: number) => { + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: any[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callback(...args); + }, delay); + }; + return debouncedCallback; + }; + + const duplicateCheck = useDebounce(async (phoneNo: string) => { if ( phoneNo && PhoneNumberValidator()(parsePhoneNumber(phoneNo) ?? "") === undefined diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index c16ef1c0e86..2a9dc875d09 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -1,4 +1,3 @@ -import { camelCase, capitalize, startCase } from "lodash-es"; import { navigate } from "raviger"; import { useTranslation } from "react-i18next"; @@ -15,6 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; +import { camelCase, capitalize, startCase } from "@/Utils/stringUtils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -270,11 +270,11 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("status")}:{" "} {" "} - {startCase(camelCase(flow.status))} + {startCase(camelCase(flow.status || ""))}
{t("label")}:{" "} - {capitalize(flow.notes)} + {capitalize(flow.notes || "")}
diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index fef2c9e4d08..e5b8a949db0 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -1,4 +1,3 @@ -import { camelCase, startCase } from "lodash-es"; import { navigate } from "raviger"; import { useState } from "react"; @@ -13,6 +12,7 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; +import { camelCase, startCase } from "@/Utils/stringUtils"; import { formatDateTime } from "@/Utils/utils"; interface SampleDetailsProps { @@ -101,7 +101,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Status{" "}
- {startCase(camelCase(itemData.status))} + {startCase(camelCase(itemData.status || ""))}
@@ -133,7 +133,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Result{" "}
- {startCase(camelCase(itemData.result))} + {startCase(camelCase(itemData.result || ""))}
From cc8880065cf60ae02259f1c5703bf3243604661a Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 14 Nov 2024 12:30:52 +0000 Subject: [PATCH 02/40] enhanced with suggestions --- src/Utils/stringUtils.ts | 11 +++++-- .../Facility/Investigations/Reports/utils.tsx | 33 ++++++++++++------- .../Investigations/ShowInvestigation.tsx | 22 +++++++++++-- .../Facility/Investigations/Table.tsx | 11 +++++++ src/components/Patient/PatientRegister.tsx | 33 +++++++++++-------- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/Utils/stringUtils.ts b/src/Utils/stringUtils.ts index 0cc2a246487..89ab3dce851 100644 --- a/src/Utils/stringUtils.ts +++ b/src/Utils/stringUtils.ts @@ -8,11 +8,16 @@ export const camelCase = (str: string) => { .replace(/([A-Z])/g, (match) => match.toLowerCase()); }; -// Capitalize the first letter of each word in a string -export const startCase = (str: string) => { +// Capitalize the first letter of each word in a string, handling edge cases +export const startCase = (str: string): string => { + if (!str) return ""; + return str + .toLowerCase() + .replace(/\s+/g, " ") + .trim() .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .map((word) => (word ? word[0].toUpperCase() + word.slice(1) : "")) .join(" "); }; diff --git a/src/components/Facility/Investigations/Reports/utils.tsx b/src/components/Facility/Investigations/Reports/utils.tsx index 5a84e6fbe60..12897e4cb3a 100644 --- a/src/components/Facility/Investigations/Reports/utils.tsx +++ b/src/components/Facility/Investigations/Reports/utils.tsx @@ -1,13 +1,24 @@ import { InvestigationResponse } from "@/components/Facility/Investigations/Reports/types"; const memoize = any>(fn: T): T => { - const cache: Record> = {}; + const cache = new Map>(); + const MAX_CACHE_SIZE = 1000; return ((...args: Parameters): ReturnType => { - const key = JSON.stringify(args); - if (!cache[key]) { - cache[key] = fn(...args); + const key = args + .map((arg) => + typeof arg === "object" + ? JSON.stringify(Object.entries(arg).sort()) + : String(arg), + ) + .join("|"); + if (!cache.has(key)) { + if (cache.size >= MAX_CACHE_SIZE) { + const firstKey: any = cache.keys().next().value; + cache.delete(firstKey); + } + cache.set(key, fn(...args)); } - return cache[key]; + return cache.get(key)!; }) as T; }; @@ -41,15 +52,14 @@ export const transformData = memoize((data: InvestigationResponse) => { ), ); + const sessionMap = new Map( + sessions.map((session, index) => [session.session_external_id, index]), + ); const reqData = groupByInvestigation.map((value: any) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val: any) => { - const sessionIndex = sessions.findIndex( - (session) => - session.session_external_id === - val.session_object.session_external_id, - ); - + const sessionIndex = + sessionMap.get(val.session_object.session_external_id) ?? -1; if (sessionIndex > -1) { sessionValues[sessionIndex] = { min: val.investigation_object.min_value, @@ -82,6 +92,7 @@ export const transformData = memoize((data: InvestigationResponse) => { sessionValues, }; }); + return { sessions, data: reqData }; }); diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index daaf25f97d9..0e298377002 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -95,10 +95,28 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { let current = changedFields; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; - if (!current[key]) current[key] = {}; + + // Protect against prototype pollution by skipping unsafe keys - crai + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; + } + + // Use Object.create(null) to prevent accidental inheritance from Object prototype - coderabbit + current[key] = current[key] || Object.create(null); current = current[key]; } - current[keys[keys.length - 1]] = value; + + const lastKey = keys[keys.length - 1]; + + // Final key assignment, ensuring no prototype pollution vulnerability - coderabbit + if ( + lastKey !== "__proto__" && + lastKey !== "constructor" && + lastKey !== "prototype" + ) { + current[lastKey] = value; + } + dispatch({ type: "set_changed_fields", changedFields }); }; diff --git a/src/components/Facility/Investigations/Table.tsx b/src/components/Facility/Investigations/Table.tsx index 0eccf004bde..0d2b5f73971 100644 --- a/src/components/Facility/Investigations/Table.tsx +++ b/src/components/Facility/Investigations/Table.tsx @@ -60,6 +60,17 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { const handleValueChange = (value: any, name: string) => { const form = { ...state }; const keys = name.split("."); + + // Validate keys to prevent prototype pollution - coderabbit suggested + if ( + keys.some((key) => + ["__proto__", "constructor", "prototype"].includes(key), + ) + ) { + console.error("Invalid object key detected"); + return; + } + let current = form; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 66f19b0b1b3..0265afd8c34 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -1,6 +1,6 @@ import careConfig from "@careConfig"; import { navigate } from "raviger"; -import { useCallback, useReducer, useRef, useState } from "react"; +import { useCallback, useEffect, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -185,6 +185,24 @@ export const parseOccupationFromExt = (occupation: Occupation) => { return occupationObject?.id; }; +const useDebounce = (callback: (...args: any[]) => void, delay: number) => { + const callbackRef = useRef(callback); + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: any[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }; + return debouncedCallback; +}; + export const PatientRegister = (props: PatientRegisterProps) => { const submitController = useRef(); const authUser = useAuthUser(); @@ -746,19 +764,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { }); }; - const useDebounce = (callback: (...args: any[]) => void, delay: number) => { - const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: any[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - callback(...args); - }, delay); - }; - return debouncedCallback; - }; - const duplicateCheck = useDebounce(async (phoneNo: string) => { if ( phoneNo && From f13a324edffe23a686388798520c5d3d1ae30d7d Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 14 Nov 2024 13:26:55 +0000 Subject: [PATCH 03/40] enhanced with suggestions 2 --- src/Utils/stringUtils.ts | 13 +++++++------ .../Facility/Investigations/Reports/utils.tsx | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Utils/stringUtils.ts b/src/Utils/stringUtils.ts index 89ab3dce851..dc8bd55cd3f 100644 --- a/src/Utils/stringUtils.ts +++ b/src/Utils/stringUtils.ts @@ -1,11 +1,10 @@ // Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. export const camelCase = (str: string) => { + if (!str) return ""; return str - .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => - index === 0 ? match.toLowerCase() : match.toUpperCase(), - ) - .replace(/\s+/g, "") - .replace(/([A-Z])/g, (match) => match.toLowerCase()); + .trim() + .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) + .replace(/^[A-Z]/, (c) => c.toLowerCase()); }; // Capitalize the first letter of each word in a string, handling edge cases @@ -23,5 +22,7 @@ export const startCase = (str: string): string => { // Capitalize the first letter of a string export const capitalize = (str: string) => { - return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); + if (!str) return ""; + if (str.length === 1) return str.toUpperCase(); + return str.charAt(0).toUpperCase() + str.slice(1); }; diff --git a/src/components/Facility/Investigations/Reports/utils.tsx b/src/components/Facility/Investigations/Reports/utils.tsx index 12897e4cb3a..349b067bc62 100644 --- a/src/components/Facility/Investigations/Reports/utils.tsx +++ b/src/components/Facility/Investigations/Reports/utils.tsx @@ -7,7 +7,9 @@ const memoize = any>(fn: T): T => { const key = args .map((arg) => typeof arg === "object" - ? JSON.stringify(Object.entries(arg).sort()) + ? arg instanceof Date + ? arg.getTime().toString() + : JSON.stringify(Object.entries(arg).sort()) : String(arg), ) .join("|"); From 17931f8cf3032744613ff8b9d7e8521c77eda292 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Fri, 15 Nov 2024 10:52:28 +0000 Subject: [PATCH 04/40] changes done, moved to utils and used tailwind --- src/Utils/.Notifications.js.swp | Bin 0 -> 12288 bytes src/Utils/Notifications.js | 2 +- src/Utils/stringUtils.ts | 28 ------------- src/Utils/utils.ts | 45 +++++++++++++++++++++ src/components/Patient/DiagnosesFilter.tsx | 22 +--------- src/components/Patient/PatientRegister.tsx | 23 ++--------- src/components/Patient/SampleDetails.tsx | 14 ++++--- src/components/Patient/SampleTestCard.tsx | 11 +++-- 8 files changed, 67 insertions(+), 78 deletions(-) create mode 100644 src/Utils/.Notifications.js.swp delete mode 100644 src/Utils/stringUtils.ts diff --git a/src/Utils/.Notifications.js.swp b/src/Utils/.Notifications.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..322de92b8df7c12f11eab96aae18725ad1837f91 GIT binary patch literal 12288 zcmeI2ON<#U(`PPU+u#xU=^?mSOu&CRspMkRlq7>6|f3e1*`&Af&ZZbyvEp}`xv{v5y|8K z|MP$UUwxFZ%itaG5;zGaK^bfY4E*{CV>iH8;4|;_xG<6sN8`2b_rz{lVt@FBPed~gUH1P8!Quo3)nKVv_GpTKqS z0k{m_1{XmS%z>xD@6h;1@GbZnXwAO__G1;W3RnfK0#<>$QK0uMBkBR7NWW6onU^w_ z1tDcJW~NAliJ-1op+{Xmmh;?e6!e-{j?v8CwU!t)wt-KDSt)tP4HENA-HySVIM<4$ zA`dU`DSDpQj*8dY0>92sp-Vap^^E5(D~*rS?s8c#qI^|@N3~E$8ZR+sHjt)XXeRX{ zIr@ty9HcN3L1HS-0PVjs#8-W39k#fy?e9mb>8Q-7`JwxPW?OQN!#;~#X9%Z zbo|x<^2s}ds4DKQxLz11qOevRGddfFBd=BJXhWwkr!9$xA&ssYUwaHjp#^gmeX;T) zOG}OP8OIW9Xy*($uSAZnHT}uwM^~KL(OnUpMcx!WniwvHGkv?^I@OMb1B{yQ=$_?a zTlCi2>T*?`mWX{a3utBrxg|=XHXL2F+O7WF&UHHqbS1kQL0g={U@_h8X~Mn_FD(mCxgx@*MUr)R8l*QYa9@F$R1CK!6c0k3ZnjQmY*uAc zgG{%XVLE?4GYv&lR}I=rlj!n&+I=a;R z@X_f|Y0xwYz43uHIjFt#9!hVK`Mi_OnymiQYo=$ewd|(MUOpZ))S7WA{KOTZXu7;s zn?H-a910R8A|VTohvHa4LFDZP*)rEP5%Nxjc9qNJA~r_)D7}?RcEy?&PRD!cLoa|Y zAuHr_+|>D~Y3R|xrM6PIZ!1|P-^#8T*F=S!FsL__lgICbI1G#~Nj7VIZ-97k@pZfcdsF_x%Opbe76}lse>wCsHF{XXRjUugf(m-q#w@l0?Kc}yuwiI)` zorvTF50F8B=a{Z4$(5)>L#T`kD>pmb5)pLOiD8#K9J2BQnW$ErOjT%VS}({4Ci8(> zATjBL!cS$vcg#7pcr}9mp^bLT31LH~k@eAGudF6~-VW75Tx*B=Za{+{F?VQZAzlhX zQKYUue-<>&U&Su { - if (!str) return ""; - return str - .trim() - .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) - .replace(/^[A-Z]/, (c) => c.toLowerCase()); -}; - -// Capitalize the first letter of each word in a string, handling edge cases -export const startCase = (str: string): string => { - if (!str) return ""; - - return str - .toLowerCase() - .replace(/\s+/g, " ") - .trim() - .split(" ") - .map((word) => (word ? word[0].toUpperCase() + word.slice(1) : "")) - .join(" "); -}; - -// Capitalize the first letter of a string -export const capitalize = (str: string) => { - if (!str) return ""; - if (str.length === 1) return str.toUpperCase(); - return str.charAt(0).toUpperCase() + str.slice(1); -}; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 3888406ad29..baff0d12e94 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -1,3 +1,5 @@ +import { useEffect, useRef } from "react"; + import { PatientModel } from "@/components/Patient/models"; import { AREACODES, IN_LANDLINE_AREA_CODES } from "@/common/constants"; @@ -544,3 +546,46 @@ export const fahrenheitToCelsius = (fahrenheit: number) => { export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; + +// Capitalize the first letter of each word in a string, handling edge cases +export const startCase = (str: string): string => { + if (!str) return ""; + + return str + .toLowerCase() + .replace(/\s+/g, " ") + .trim() + .split(" ") + .map((word) => (word ? word[0].toUpperCase() + word.slice(1) : "")) + .join(" "); +}; + +// Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. +export const camelCase = (str: string) => { + if (!str) return ""; + return str + .trim() + .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) + .replace(/^[A-Z]/, (c) => c.toLowerCase()); +}; + +export const useDebounce = ( + callback: (...args: string[]) => void, + delay: number, +) => { + const callbackRef = useRef(callback); + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: string[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }; + return debouncedCallback; +}; diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index 7cc5e1fd3f3..3165705880f 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; @@ -8,7 +8,7 @@ import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/Autoc import { Error } from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { mergeQueryOptions } from "@/Utils/utils"; +import { mergeQueryOptions, useDebounce } from "@/Utils/utils"; export const FILTER_BY_DIAGNOSES_KEYS = [ "diagnoses", @@ -34,24 +34,6 @@ interface Props { onChange: (event: { name: DiagnosesFilterKey; value: string }) => void; } -function useDebounce(callback: (...args: any[]) => void, delay: number) { - const timeoutRef = useRef | null>(null); - - const debouncedCallback = useCallback( - (...args: any[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - callback(...args); - }, delay); - }, - [callback, delay], - ); - - return debouncedCallback; -} - export default function DiagnosesFilter(props: Props) { const { t } = useTranslation(); const [diagnoses, setDiagnoses] = useState([]); diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 0265afd8c34..3d9d038bd9d 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -1,6 +1,6 @@ import careConfig from "@careConfig"; import { navigate } from "raviger"; -import { useCallback, useEffect, useReducer, useRef, useState } from "react"; +import { useCallback, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -74,7 +74,7 @@ import { usePubSub } from "@/Utils/pubsubContext"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; -import { startCase } from "@/Utils/stringUtils"; +import { startCase } from "@/Utils/utils"; import { compareBy, dateQueryString, @@ -82,6 +82,7 @@ import { includesIgnoreCase, parsePhoneNumber, scrollTo, + useDebounce, } from "@/Utils/utils"; export type PatientForm = PatientModel & @@ -185,24 +186,6 @@ export const parseOccupationFromExt = (occupation: Occupation) => { return occupationObject?.id; }; -const useDebounce = (callback: (...args: any[]) => void, delay: number) => { - const callbackRef = useRef(callback); - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - - const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: any[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - callbackRef.current(...args); - }, delay); - }; - return debouncedCallback; -}; - export const PatientRegister = (props: PatientRegisterProps) => { const submitController = useRef(); const authUser = useAuthUser(); diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 2a9dc875d09..acc216e336b 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { camelCase, capitalize, startCase } from "@/Utils/stringUtils"; +import { camelCase } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -270,11 +270,11 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("status")}:{" "} {" "} - {startCase(camelCase(flow.status || ""))} + {camelCase(flow.status || "")}
{t("label")}:{" "} - {capitalize(flow.notes || "")} + {flow.notes}
@@ -370,7 +370,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {startCase(camelCase(sampleDetails.doctor_name))} + + {camelCase(sampleDetails.doctor_name)} +
)} {sampleDetails?.diagnosis && ( @@ -451,7 +453,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {startCase(camelCase(sampleDetails.sample_type))} + + {camelCase(sampleDetails.sample_type)} + )} {sampleDetails?.sample_type === "OTHER TYPE" && ( diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index e5b8a949db0..bdb2df7a5a2 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -12,8 +12,7 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; -import { camelCase, startCase } from "@/Utils/stringUtils"; -import { formatDateTime } from "@/Utils/utils"; +import { camelCase, formatDateTime } from "@/Utils/utils"; interface SampleDetailsProps { facilityId: number; @@ -101,7 +100,9 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Status{" "}
- {startCase(camelCase(itemData.status || ""))} + + {camelCase(itemData.status || "")} +
@@ -133,7 +134,9 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Result{" "}
- {startCase(camelCase(itemData.result || ""))} + + {camelCase(itemData.result || "")} +
From de43d86cdcfdcb6d3202c079cb89961053d38611 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Fri, 15 Nov 2024 11:39:41 +0000 Subject: [PATCH 05/40] trying to fixing cypress tests --- src/components/Patient/DiagnosesFilter.tsx | 2 +- src/components/Patient/PatientRegister.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index 3165705880f..306017ea380 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -70,7 +70,7 @@ export default function DiagnosesFilter(props: Props) { const debouncedQuery = useDebounce((query: string) => { refetch({ query: { query } }); - }, 300); + }, 100); return ( { } } } - }, 300); + }, 100); const handleDialogClose = (action: string) => { if (action === "transfer") { From 1a136f420ce6aecd3fff7403a4faf1ce7da3168e Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Fri, 15 Nov 2024 12:03:41 +0000 Subject: [PATCH 06/40] trying to build fix 2 --- src/components/Patient/DiagnosesFilter.tsx | 2 +- src/components/Patient/PatientRegister.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index 306017ea380..31cbce47d7e 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -70,7 +70,7 @@ export default function DiagnosesFilter(props: Props) { const debouncedQuery = useDebounce((query: string) => { refetch({ query: { query } }); - }, 100); + }, 0); return ( { } } } - }, 100); + }, 0); const handleDialogClose = (action: string) => { if (action === "transfer") { From e2abfa6ed085ee9b5b3489e893ea58a0c5af3cee Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 21 Nov 2024 15:06:21 +0000 Subject: [PATCH 07/40] updates on comments, moving debounce and nested code to proper files --- src/Utils/.Notifications.js.swp | Bin 12288 -> 0 bytes src/Utils/Notifications.js | 2 +- src/Utils/utils.ts | 52 +++++++++++------- .../Investigations/ShowInvestigation.tsx | 26 +-------- src/components/Patient/DiagnosesFilter.tsx | 6 +- src/components/Patient/PatientRegister.tsx | 4 +- src/hooks/useDebounce.ts | 22 ++++++++ 7 files changed, 62 insertions(+), 50 deletions(-) delete mode 100644 src/Utils/.Notifications.js.swp create mode 100644 src/hooks/useDebounce.ts diff --git a/src/Utils/.Notifications.js.swp b/src/Utils/.Notifications.js.swp deleted file mode 100644 index 322de92b8df7c12f11eab96aae18725ad1837f91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2ON<#U(`PPU+u#xU=^?mSOu&CRspMkRlq7>6|f3e1*`&Af&ZZbyvEp}`xv{v5y|8K z|MP$UUwxFZ%itaG5;zGaK^bfY4E*{CV>iH8;4|;_xG<6sN8`2b_rz{lVt@FBPed~gUH1P8!Quo3)nKVv_GpTKqS z0k{m_1{XmS%z>xD@6h;1@GbZnXwAO__G1;W3RnfK0#<>$QK0uMBkBR7NWW6onU^w_ z1tDcJW~NAliJ-1op+{Xmmh;?e6!e-{j?v8CwU!t)wt-KDSt)tP4HENA-HySVIM<4$ zA`dU`DSDpQj*8dY0>92sp-Vap^^E5(D~*rS?s8c#qI^|@N3~E$8ZR+sHjt)XXeRX{ zIr@ty9HcN3L1HS-0PVjs#8-W39k#fy?e9mb>8Q-7`JwxPW?OQN!#;~#X9%Z zbo|x<^2s}ds4DKQxLz11qOevRGddfFBd=BJXhWwkr!9$xA&ssYUwaHjp#^gmeX;T) zOG}OP8OIW9Xy*($uSAZnHT}uwM^~KL(OnUpMcx!WniwvHGkv?^I@OMb1B{yQ=$_?a zTlCi2>T*?`mWX{a3utBrxg|=XHXL2F+O7WF&UHHqbS1kQL0g={U@_h8X~Mn_FD(mCxgx@*MUr)R8l*QYa9@F$R1CK!6c0k3ZnjQmY*uAc zgG{%XVLE?4GYv&lR}I=rlj!n&+I=a;R z@X_f|Y0xwYz43uHIjFt#9!hVK`Mi_OnymiQYo=$ewd|(MUOpZ))S7WA{KOTZXu7;s zn?H-a910R8A|VTohvHa4LFDZP*)rEP5%Nxjc9qNJA~r_)D7}?RcEy?&PRD!cLoa|Y zAuHr_+|>D~Y3R|xrM6PIZ!1|P-^#8T*F=S!FsL__lgICbI1G#~Nj7VIZ-97k@pZfcdsF_x%Opbe76}lse>wCsHF{XXRjUugf(m-q#w@l0?Kc}yuwiI)` zorvTF50F8B=a{Z4$(5)>L#T`kD>pmb5)pLOiD8#K9J2BQnW$ErOjT%VS}({4Ci8(> zATjBL!cS$vcg#7pcr}9mp^bLT31LH~k@eAGudF6~-VW75Tx*B=Za{+{F?VQZAzlhX zQKYUue-<>&U&Su { .replace(/^[A-Z]/, (c) => c.toLowerCase()); }; -export const useDebounce = ( - callback: (...args: string[]) => void, - delay: number, -) => { - const callbackRef = useRef(callback); - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - - const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: string[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); +export function setNestedValueSafely( + obj: Record, + path: string, + value: any, +) { + const keys = path.split("."); + let current = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + + // Protect against prototype pollution by skipping unsafe keys + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; } - timeoutRef.current = setTimeout(() => { - callbackRef.current(...args); - }, delay); - }; - return debouncedCallback; -}; + + // Use Object.create(null) to prevent accidental inheritance from Object prototype + current[key] = current[key] || Object.create(null); + current = current[key]; + } + + const lastKey = keys[keys.length - 1]; + + // Final key assignment, ensuring no prototype pollution vulnerability + if ( + lastKey !== "__proto__" && + lastKey !== "constructor" && + lastKey !== "prototype" + ) { + current[lastKey] = value; + } +} diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index 0e298377002..15c676220fd 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -9,6 +9,7 @@ import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; +import { setNestedValueSafely } from "@/Utils/utils"; const initialState = { changedFields: {}, @@ -91,31 +92,8 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - const keys = name.split("."); - let current = changedFields; - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - - // Protect against prototype pollution by skipping unsafe keys - crai - if (key === "__proto__" || key === "constructor" || key === "prototype") { - continue; - } - - // Use Object.create(null) to prevent accidental inheritance from Object prototype - coderabbit - current[key] = current[key] || Object.create(null); - current = current[key]; - } - const lastKey = keys[keys.length - 1]; - - // Final key assignment, ensuring no prototype pollution vulnerability - coderabbit - if ( - lastKey !== "__proto__" && - lastKey !== "constructor" && - lastKey !== "prototype" - ) { - current[lastKey] = value; - } + setNestedValueSafely(changedFields, name, value); dispatch({ type: "set_changed_fields", changedFields }); }; diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index 31cbce47d7e..e0d8c543ce9 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -5,10 +5,12 @@ import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/AutocompleteMultiselect"; +import useDebounce from "@/hooks/useDebounce"; + import { Error } from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { mergeQueryOptions, useDebounce } from "@/Utils/utils"; +import { mergeQueryOptions } from "@/Utils/utils"; export const FILTER_BY_DIAGNOSES_KEYS = [ "diagnoses", @@ -70,7 +72,7 @@ export default function DiagnosesFilter(props: Props) { const debouncedQuery = useDebounce((query: string) => { refetch({ query: { query } }); - }, 0); + }, 300); return ( { } } } - }, 0); + }, 300); const handleDialogClose = (action: string) => { if (action === "transfer") { diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 00000000000..961a976d893 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef } from "react"; + +export default function useDebounce( + callback: (...args: string[]) => void, + delay: number, +) { + const callbackRef = useRef(callback); + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: string[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }; + return debouncedCallback; +} From a8950c343ee0eda7bb48fca7c9a4bca1ac3302e3 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 23 Nov 2024 13:27:01 +0000 Subject: [PATCH 08/40] fixing cypress tests --- .../e2e/patient_spec/PatientConsultationCreation.cy.ts | 6 ++---- cypress/e2e/patient_spec/PatientRegistration.cy.ts | 1 + cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts | 8 ++++++++ src/Utils/utils.ts | 8 +++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index 3eacd9b718f..8e46dc86ede 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -201,10 +201,8 @@ describe("Patient Consultation in multiple combination", () => { cy.clickSubmitButton("Create Consultation"); cy.verifyNotification("Consultation created successfully"); // verify the data and death report - patientConsultationPage.verifyTextInConsultation( - "#consultation-buttons", - "EXPIRED", - ); + cy.get("#consultation-buttons").should("be.visible"); + cy.contains("#consultation-buttons", "EXPIRED").should("exist"); patientConsultationPage.clickPatientDetails(); patientDeathReport.clickDeathReport(); patientDeathReport.verifyDeathReportAutofill( diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index c3647ceac3a..3863e302438 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -83,6 +83,7 @@ describe("Patient Creation with consultation", () => { patientPage.createPatient(); patientPage.selectFacility(patientFacility); patientPage.patientformvisibility(); + cy.get("div:contains('Age')").scrollIntoView().should("be.visible").click(); // Patient Details page patientPage.typePatientPhoneNumber(phone_number); patientPage.checkPhoneNumberIsEmergencyNumber(); diff --git a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts index da4d8aabd20..b016e0f5aae 100644 --- a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts +++ b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts @@ -42,6 +42,8 @@ describe("Sample Test", () => { patientConsultationPage.verifyPatientDetailsResponse(); // Visit SampleRequest Page sampleTestPage.visitSampleRequestPage(); + // Wait for necessary APIs + cy.intercept("GET", "/api/sample-test-status").as("sampleTestStatus"); // Fill Sample Test Request Form sampleTestPage.selectSampleType(sampleTestType); sampleTestPage.selectIcmrCategory(icmrCategory); @@ -60,6 +62,12 @@ describe("Sample Test", () => { cy.clickSubmitButton("Confirm your request to send sample for testing"); sampleTestPage.verifySampleTestReq(); cy.verifyNotification("Sample test created successfully"); + // Wait for status element to load + cy.wait("@sampleTestStatus"); + cy.get("#sample-test-status", { timeout: 20000 }) + .should("exist") + .and("contain.text", sampleTestStatus); + // Check the updated request history sampleTestPage.checkRequestHistory( sampleTestStatus, diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 941a4ca5a4f..c042d01d182 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -553,9 +553,11 @@ export const startCase = (str: string): string => { .toLowerCase() .replace(/\s+/g, " ") .trim() - .split(" ") - .map((word) => (word ? word[0].toUpperCase() + word.slice(1) : "")) - .join(" "); + .split(/[\s.'-]+/) + .map((word) => { + return word ? word[0].toUpperCase() + word.slice(1) : ""; + }) + .join(" "); }; // Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. From 0d2e876a596458c06dd6eaada0619082d0b051e4 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 23 Nov 2024 14:03:18 +0000 Subject: [PATCH 09/40] fixing cypress tests --- .../e2e/patient_spec/PatientConsultationCreation.cy.ts | 6 ++++-- cypress/e2e/patient_spec/PatientRegistration.cy.ts | 1 - cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts | 8 -------- cypress/pageobject/Patient/PatientConsultation.ts | 2 +- cypress/pageobject/Patient/PatientCreation.ts | 4 +++- cypress/pageobject/Sample/SampleTestCreate.ts | 2 +- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index 8e46dc86ede..3eacd9b718f 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -201,8 +201,10 @@ describe("Patient Consultation in multiple combination", () => { cy.clickSubmitButton("Create Consultation"); cy.verifyNotification("Consultation created successfully"); // verify the data and death report - cy.get("#consultation-buttons").should("be.visible"); - cy.contains("#consultation-buttons", "EXPIRED").should("exist"); + patientConsultationPage.verifyTextInConsultation( + "#consultation-buttons", + "EXPIRED", + ); patientConsultationPage.clickPatientDetails(); patientDeathReport.clickDeathReport(); patientDeathReport.verifyDeathReportAutofill( diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index 3863e302438..c3647ceac3a 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -83,7 +83,6 @@ describe("Patient Creation with consultation", () => { patientPage.createPatient(); patientPage.selectFacility(patientFacility); patientPage.patientformvisibility(); - cy.get("div:contains('Age')").scrollIntoView().should("be.visible").click(); // Patient Details page patientPage.typePatientPhoneNumber(phone_number); patientPage.checkPhoneNumberIsEmergencyNumber(); diff --git a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts index b016e0f5aae..da4d8aabd20 100644 --- a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts +++ b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts @@ -42,8 +42,6 @@ describe("Sample Test", () => { patientConsultationPage.verifyPatientDetailsResponse(); // Visit SampleRequest Page sampleTestPage.visitSampleRequestPage(); - // Wait for necessary APIs - cy.intercept("GET", "/api/sample-test-status").as("sampleTestStatus"); // Fill Sample Test Request Form sampleTestPage.selectSampleType(sampleTestType); sampleTestPage.selectIcmrCategory(icmrCategory); @@ -62,12 +60,6 @@ describe("Sample Test", () => { cy.clickSubmitButton("Confirm your request to send sample for testing"); sampleTestPage.verifySampleTestReq(); cy.verifyNotification("Sample test created successfully"); - // Wait for status element to load - cy.wait("@sampleTestStatus"); - cy.get("#sample-test-status", { timeout: 20000 }) - .should("exist") - .and("contain.text", sampleTestStatus); - // Check the updated request history sampleTestPage.checkRequestHistory( sampleTestStatus, diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index c8046a39a9e..b5f13803d27 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -96,7 +96,7 @@ export class PatientConsultationPage { verifyTextInConsultation(selector: string, text: string) { cy.get(selector).scrollIntoView(); - cy.get(selector).contains(text).should("be.visible"); + cy.get(selector).contains(text, { timeout: 20000 }).should("be.visible"); } typeReferringFacility(referringFacility: string) { diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index a38b8ab6a5c..2767ba6d035 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -7,7 +7,9 @@ export class PatientPage { cy.intercept("GET", "**/api/v1/facility/**").as("getFacilities"); cy.get("#add-patient-details").should("be.visible"); cy.get("#add-patient-details").click(); - cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); + cy.wait("@getFacilities", { timeout: 20000 }) + .its("response.statusCode") + .should("eq", 200); } visitPatient(patientName: string) { diff --git a/cypress/pageobject/Sample/SampleTestCreate.ts b/cypress/pageobject/Sample/SampleTestCreate.ts index cd2e93e5072..b554d82edb0 100644 --- a/cypress/pageobject/Sample/SampleTestCreate.ts +++ b/cypress/pageobject/Sample/SampleTestCreate.ts @@ -1,7 +1,7 @@ export class SampleTestPage { visitSampleRequestPage(): void { cy.verifyAndClickElement("#sample-request-btn", "Request Sample Test"); - cy.url().should("include", "/sample-test"); + cy.url().should("include", "/sample-test", { timeout: 20000 }); } selectSampleType(option: string): void { From e0b6526c33e0f85aadd8f95b392b937b708f0040 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 25 Nov 2024 14:08:49 +0000 Subject: [PATCH 10/40] fixing tests 2+4 --- src/components/Patient/DiagnosesFilter.tsx | 9 ++++----- src/components/Patient/PatientRegister.tsx | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index e0d8c543ce9..f133138591c 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -70,10 +70,6 @@ export default function DiagnosesFilter(props: Props) { }); }, [props.value]); - const debouncedQuery = useDebounce((query: string) => { - refetch({ query: { query } }); - }, 300); - return ( obj.id)} optionLabel={(option) => option.label} optionValue={(option) => option} - onQuery={debouncedQuery} + onQuery={useDebounce( + (query: string) => refetch({ query: { query } }), + 300, + )} isLoading={loading} /> ); diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 223206d5a0a..aef509dc8fa 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -777,6 +777,11 @@ export const PatientRegister = (props: PatientRegisterProps) => { }); } } + } else { + setStatusDialog({ + show: false, + patientList: [], + }); } }, 300); From 9fff8861d70fff58ba6761fe2cfef1c09004dc96 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 27 Nov 2024 14:15:27 +0000 Subject: [PATCH 11/40] handling null string --- src/components/Patient/PatientRegister.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 35fe25ad702..dec9c54920a 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -656,7 +656,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { ? formData.last_vaccinated_date : null : null, - name: startCase(formData.name.toLowerCase()), + name: formData.name ? startCase(formData.name.toLowerCase()) : "", pincode: formData.pincode ? formData.pincode : undefined, gender: Number(formData.gender), nationality: formData.nationality, From b029acaa0a883c63cc8a22a6084d5aefd4ceb9b5 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 27 Nov 2024 15:02:19 +0000 Subject: [PATCH 12/40] added missing id --- src/Utils/utils.ts | 4 ++-- src/components/Patient/SampleDetails.tsx | 14 ++++++-------- src/components/Patient/SampleTestCard.tsx | 20 +++++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index c042d01d182..824d9733c43 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -547,7 +547,7 @@ export const keysOf = (obj: T) => { // Capitalize the first letter of each word in a string, handling edge cases export const startCase = (str: string): string => { - if (!str) return ""; + if (!str || str.length == 0) return ""; return str .toLowerCase() @@ -562,7 +562,7 @@ export const startCase = (str: string): string => { // Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. export const camelCase = (str: string) => { - if (!str) return ""; + if (!str || str.length == 0) return ""; return str .trim() .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 9b2721fe33a..5a02bfe2281 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { camelCase } from "@/Utils/utils"; +import { camelCase, startCase } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -270,11 +270,11 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("status")}:{" "} {" "} - {camelCase(flow.status || "")} + {startCase(camelCase(String(flow.status || "")))}
{t("label")}:{" "} - {flow.notes} + {startCase(String(flow.notes || ""))}
@@ -370,8 +370,8 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - - {camelCase(sampleDetails.doctor_name)} + + {startCase(camelCase(sampleDetails.doctor_name))}
)} @@ -455,9 +455,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - - {camelCase(sampleDetails.sample_type)} - + {startCase(camelCase(sampleDetails.sample_type))} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index 47a92b543fb..b424c535b97 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -12,7 +12,7 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; -import { camelCase, formatDateTime } from "@/Utils/utils"; +import { camelCase, formatDateTime, startCase } from "@/Utils/utils"; interface SampleDetailsProps { facilityId: string; @@ -100,10 +100,11 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Status{" "}
-
- - {camelCase(itemData.status || "")} - +
+ {startCase(camelCase(itemData.status || ""))}
@@ -140,10 +141,11 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Result{" "}
-
- - {camelCase(itemData.result || "")} - +
+ {startCase(camelCase(itemData.result || ""))}
From 30a9311abc84edd030dbfba5146387d4719e45a1 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 27 Nov 2024 15:15:00 +0000 Subject: [PATCH 13/40] fix2 --- src/components/Patient/SampleTestCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index b424c535b97..e1bd5b83947 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -104,7 +104,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { id="sample-test-status" className="mt-1 overflow-x-scroll whitespace-normal break-words text-sm font-medium leading-5" > - {startCase(camelCase(itemData.status || ""))} + {startCase(camelCase(String(itemData.status)))} @@ -145,7 +145,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { id="sample-test-result" className="mt-1 overflow-x-scroll whitespace-normal break-words text-sm font-medium leading-5" > - {startCase(camelCase(itemData.result || ""))} + {startCase(camelCase(String(itemData.result)))} From 8647be9a7d6de09f79d3ff69d515ebef81082757 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 28 Nov 2024 13:38:02 +0000 Subject: [PATCH 14/40] fixed cypress 4 --- src/Utils/utils.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 824d9733c43..19240c60a73 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -547,22 +547,20 @@ export const keysOf = (obj: T) => { // Capitalize the first letter of each word in a string, handling edge cases export const startCase = (str: string): string => { - if (!str || str.length == 0) return ""; - + if (!str || str.length === 0) return ""; return str - .toLowerCase() + .replace(/([A-Z])/g, " $1") + .replace(/[_-]+/g, " ") .replace(/\s+/g, " ") .trim() - .split(/[\s.'-]+/) - .map((word) => { - return word ? word[0].toUpperCase() + word.slice(1) : ""; - }) + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(" "); }; // Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. -export const camelCase = (str: string) => { - if (!str || str.length == 0) return ""; +export const camelCase = (str: string): string => { + if (!str || str.length === 0) return ""; return str .trim() .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) From 598aae4ff785185e9e9a726995d6b3800eec3d2e Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 28 Nov 2024 14:01:34 +0000 Subject: [PATCH 15/40] fixed camel and start case --- src/Utils/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 19240c60a73..e2dfd37690a 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -549,7 +549,7 @@ export const keysOf = (obj: T) => { export const startCase = (str: string): string => { if (!str || str.length === 0) return ""; return str - .replace(/([A-Z])/g, " $1") + .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_-]+/g, " ") .replace(/\s+/g, " ") .trim() @@ -563,8 +563,8 @@ export const camelCase = (str: string): string => { if (!str || str.length === 0) return ""; return str .trim() - .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")) - .replace(/^[A-Z]/, (c) => c.toLowerCase()); + .toLowerCase() + .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")); }; export function setNestedValueSafely( From 449d5749b51fedcfb3d28edbd416ab01b655edba Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Fri, 29 Nov 2024 12:21:36 +0000 Subject: [PATCH 16/40] added autoCapitalize for accepting any patient name --- src/components/Form/Form.tsx | 3 +-- src/components/Form/FormFields/TextFormField.tsx | 1 + src/components/Patient/PatientRegister.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 4f06a441f68..b1cafd5018e 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -59,8 +59,7 @@ const Form = ({ if (validate) { const errors = Object.fromEntries( Object.entries(validate(state.form)).filter( - ([_key, value]) => - value !== "" && value !== null && value !== undefined, + ([_key, value]) => value !== "" && value !== undefined, ), ) as FormErrors; diff --git a/src/components/Form/FormFields/TextFormField.tsx b/src/components/Form/FormFields/TextFormField.tsx index fd948e0a264..c3754775526 100644 --- a/src/components/Form/FormFields/TextFormField.tsx +++ b/src/components/Form/FormFields/TextFormField.tsx @@ -101,6 +101,7 @@ const TextFormField = forwardRef((props: TextFormFieldProps, ref) => { name={field.name} value={field.value} required={field.required} + autoCapitalize={props.autoCapitalize || "words"} onChange={(e) => field.handleChange(e.target.value)} /> {props.clearable && field.value && ( diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index dec9c54920a..a7098c24264 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -77,7 +77,6 @@ import { usePubSub } from "@/Utils/pubsubContext"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; -import { startCase } from "@/Utils/utils"; import { compareBy, dateQueryString, @@ -656,7 +655,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { ? formData.last_vaccinated_date : null : null, - name: formData.name ? startCase(formData.name.toLowerCase()) : "", + name: formData.name, pincode: formData.pincode ? formData.pincode : undefined, gender: Number(formData.gender), nationality: formData.nationality, @@ -1027,6 +1026,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { {...field("name")} type="text" label={"Name"} + autoCapitalize="words" />
From 00e3ceda2c6153194d96c8b006ce556a45b6d16c Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Fri, 29 Nov 2024 13:11:36 +0000 Subject: [PATCH 17/40] capitalize --- src/components/Patient/SampleTestCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index db0b85215c3..ed4a1df3502 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -12,7 +12,7 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; -import { camelCase, formatDateTime, startCase } from "@/Utils/utils"; +import { formatDateTime } from "@/Utils/utils"; interface SampleDetailsProps { facilityId: string; @@ -143,9 +143,9 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
- {startCase(camelCase(String(itemData.result)))} + {itemData.result}
From d3a4d981793eaaf4e4065a55d5b5a8f5922b06be Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 30 Nov 2024 05:20:11 +0000 Subject: [PATCH 18/40] added Status using mapping and translations --- public/locale/en.json | 4 ++++ public/locale/hi.json | 4 ++++ public/locale/kn.json | 4 ++++ public/locale/ml.json | 4 ++++ public/locale/mr.json | 5 +++++ public/locale/ta.json | 4 ++++ src/components/Facility/Investigations/Table.tsx | 1 - src/components/Patient/SampleDetails.tsx | 2 +- 8 files changed, 26 insertions(+), 2 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index cd921bef06a..c84d5f4eabd 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -181,6 +181,10 @@ "ROUNDS_TYPE__NORMAL": "Brief Update", "ROUNDS_TYPE__TELEMEDICINE": "Tele-medicine Log", "ROUNDS_TYPE__VENTILATOR": "Detailed Update", + "SAMPLE_TEST_HISTORY__APPROVED": "Approved", + "SAMPLE_TEST_HISTORY__COMPLETED": "Completed", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "Received At Lab", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "Request Submitted", "SLEEP__EXCESSIVE": "Excessive", "SLEEP__NO_SLEEP": "No sleep", "SLEEP__SATISFACTORY": "Satisfactory", diff --git a/public/locale/hi.json b/public/locale/hi.json index b3c172021dd..1e36eb4b9cc 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -90,6 +90,10 @@ "RESPIRATORY_SUPPORT__OXYGEN_SUPPORT": "ऑक्सीजन सहायता", "RESPIRATORY_SUPPORT__UNKNOWN": "कोई नहीं", "Resource": "संसाधन", + "SAMPLE_TEST_HISTORY__APPROVED": "मंजूर", + "SAMPLE_TEST_HISTORY__COMPLETED": "पूरा हुआ", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "प्रयोगशाला में प्राप्त हुआ", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "अनुरोध प्रस्तुत किया गया", "SORT_OPTIONS__-bed__name": "बिस्तर संख्या एन-1", "SORT_OPTIONS__-category_severity": "सर्वोच्च गंभीरता श्रेणी पहले", "SORT_OPTIONS__-created_date": "नवीनतम निर्माण तिथि पहले", diff --git a/public/locale/kn.json b/public/locale/kn.json index faf50da3a23..db9bb0ac522 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -90,6 +90,10 @@ "RESPIRATORY_SUPPORT__OXYGEN_SUPPORT": "ಆಮ್ಲಜನಕ ಬೆಂಬಲ", "RESPIRATORY_SUPPORT__UNKNOWN": "ಯಾವುದೂ ಇಲ್ಲ", "Resource": "ಸಂಪನ್ಮೂಲ", + "SAMPLE_TEST_HISTORY__APPROVED": "ಮಂಜೂರು", + "SAMPLE_TEST_HISTORY__COMPLETED": "ಪೂರ್ಣಗೊಂಡಿದೆ", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "ಪ್ರಯೋಗಾಲಯದಲ್ಲಿ ಸ್ವೀಕರಿಸಲಾಗಿದೆ", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "ಅರ್ಜಿಯನ್ನು ಸಲ್ಲಿಸಲಾಗಿದೆ", "SORT_OPTIONS__-bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1", "SORT_OPTIONS__-category_severity": "ಅತ್ಯಧಿಕ ತೀವ್ರತೆಯ ವಿಭಾಗ ಮೊದಲು", "SORT_OPTIONS__-created_date": "ಇತ್ತೀಚಿಗೆ ರಚಿಸಿದ ದಿನಾಂಕ ಮೊದಲು", diff --git a/public/locale/ml.json b/public/locale/ml.json index 33dadcf9fb7..b359de99dc4 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -90,6 +90,10 @@ "RESPIRATORY_SUPPORT__OXYGEN_SUPPORT": "ഓക്സിജൻ പിന്തുണ", "RESPIRATORY_SUPPORT__UNKNOWN": "ഒന്നുമില്ല", "Resource": "സഹായം", + "SAMPLE_TEST_HISTORY__APPROVED": "അംഗീകരിച്ചു", + "SAMPLE_TEST_HISTORY__COMPLETED": "പൂർത്തിയായി", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "ലബോറട്ടറിയിൽ ലഭിച്ചു", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "അഭ്യർത്ഥന സമർപ്പിച്ചു", "SORT_OPTIONS__-bed__name": "കിടക്ക നമ്പർ N-1", "SORT_OPTIONS__-category_severity": "ഏറ്റവും ഉയർന്ന തീവ്രത വിഭാഗം ആദ്യം", "SORT_OPTIONS__-created_date": "ആദ്യം സൃഷ്ടിച്ച ഏറ്റവും പുതിയ തീയതി", diff --git a/public/locale/mr.json b/public/locale/mr.json index 543278bf867..c4e646f131e 100644 --- a/public/locale/mr.json +++ b/public/locale/mr.json @@ -1,4 +1,8 @@ { + "SAMPLE_TEST_HISTORY__APPROVED": "मंजूर", + "SAMPLE_TEST_HISTORY__COMPLETED": "पूर्ण झाले", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "प्रयोगशाळेत प्राप्त झाले", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "विनंती सादर केली", "Assets": "मालमत्ता", "Dashboard": "डॅशबोर्ड", "District": "जिल्हा", @@ -15,6 +19,7 @@ "Sample Test": "नमुना तपासणी ", "Shifting": "शिफ्टिंग", "State": "राज्य", + "status": "स्थिती", "Users": "युजर्स", "View Facility": "हॉस्पिटल पाहा", "Ward": "वॉर्ड", diff --git a/public/locale/ta.json b/public/locale/ta.json index 694efd52e20..6962977786c 100644 --- a/public/locale/ta.json +++ b/public/locale/ta.json @@ -90,6 +90,10 @@ "RESPIRATORY_SUPPORT__OXYGEN_SUPPORT": "ஆக்ஸிஜன் ஆதரவு", "RESPIRATORY_SUPPORT__UNKNOWN": "இல்லை", "Resource": "வளம்", + "SAMPLE_TEST_HISTORY__APPROVED": "ஒப்புதலளிக்கப்பட்டது", + "SAMPLE_TEST_HISTORY__COMPLETED": "முடிந்தது", + "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "லேபரட்டரியில் பெறப்பட்டது", + "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "கோரிக்கை சமர்ப்பிக்கப்பட்டது", "SORT_OPTIONS__-bed__name": "படுக்கை எண். N-1", "SORT_OPTIONS__-category_severity": "அதிக தீவிரத்தன்மை பிரிவு முதலில்", "SORT_OPTIONS__-created_date": "புதிதாக உருவாக்கப்பட்ட தேதி முதலில்", diff --git a/src/components/Facility/Investigations/Table.tsx b/src/components/Facility/Investigations/Table.tsx index 0d2b5f73971..49dd924480b 100644 --- a/src/components/Facility/Investigations/Table.tsx +++ b/src/components/Facility/Investigations/Table.tsx @@ -67,7 +67,6 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { ["__proto__", "constructor", "prototype"].includes(key), ) ) { - console.error("Invalid object key detected"); return; } diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index bf1dd71beba..ceb94b7b440 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -270,7 +270,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("status")}:{" "} {" "} - {flow.status} + {t(`SAMPLE_TEST_HISTORY__${flow.status}`) || "Unknown"}
{t("label")}:{" "} From e6ec037c5004ef4aa638b14ce11fbbefeeedfbe6 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 30 Nov 2024 06:42:31 +0000 Subject: [PATCH 19/40] added translations for sample-test-result --- public/locale/en.json | 4 ++++ src/components/Patient/SampleTestCard.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locale/en.json b/public/locale/en.json index c84d5f4eabd..6b751be816a 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -185,6 +185,10 @@ "SAMPLE_TEST_HISTORY__COMPLETED": "Completed", "SAMPLE_TEST_HISTORY__RECEIVED_AT_LAB": "Received At Lab", "SAMPLE_TEST_HISTORY__REQUEST_SUBMITTED": "Request Submitted", + "SAMPLE_TEST_RESULT__AWAITING": "Awaiting", + "SAMPLE_TEST_RESULT__INVALID": "Invalid", + "SAMPLE_TEST_RESULT__NEGATIVE": "Negative", + "SAMPLE_TEST_RESULT__POSITIVE": "Positive", "SLEEP__EXCESSIVE": "Excessive", "SLEEP__NO_SLEEP": "No sleep", "SLEEP__SATISFACTORY": "Satisfactory", diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index f9c6f728951..f1d59980c23 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -147,7 +147,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { id="sample-test-result" className="mt-1 overflow-x-scroll whitespace-normal break-words text-sm font-medium capitalize leading-5" > - {itemData.result} + {t(`SAMPLE_TEST_RESULT__${itemData.result}`) || "Unknown"}
From 07ae5a205eac976c67b334a42e88b08fbeb24547 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 30 Nov 2024 07:26:27 +0000 Subject: [PATCH 20/40] fix --- src/Utils/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index e2dfd37690a..83389501233 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -545,7 +545,7 @@ export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; -// Capitalize the first letter of each word in a string, handling edge cases +// Capitalizes the first letter of each word in a string, handling edge cases export const startCase = (str: string): string => { if (!str || str.length === 0) return ""; return str @@ -558,7 +558,7 @@ export const startCase = (str: string): string => { .join(" "); }; -// Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces. +// Converts a string to camelCase format, first word to lowercase and each subsequent word - uppercase letter, with no spaces. export const camelCase = (str: string): string => { if (!str || str.length === 0) return ""; return str From 4175e1af45cadc4d102d31fe1fec95d8c85124d0 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sat, 30 Nov 2024 19:25:47 +0000 Subject: [PATCH 21/40] removed setNestedValueSafely, added useOmitBy.ts and adjusted useDeboune's --- src/Utils/utils.ts | 33 ------------- .../Investigations/ShowInvestigation.tsx | 22 +++++++-- src/components/Form/AutoCompleteAsync.tsx | 47 +++++++++---------- src/components/Form/Form.tsx | 16 +++++-- src/components/Patient/DiagnosesFilter.tsx | 10 ++-- src/hooks/useDebounce.ts | 6 +-- src/hooks/useOmitBy.ts | 16 +++++++ 7 files changed, 78 insertions(+), 72 deletions(-) create mode 100644 src/hooks/useOmitBy.ts diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 83389501233..e3c95c5cdaf 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -566,36 +566,3 @@ export const camelCase = (str: string): string => { .toLowerCase() .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")); }; - -export function setNestedValueSafely( - obj: Record, - path: string, - value: any, -) { - const keys = path.split("."); - let current = obj; - - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - - // Protect against prototype pollution by skipping unsafe keys - if (key === "__proto__" || key === "constructor" || key === "prototype") { - continue; - } - - // Use Object.create(null) to prevent accidental inheritance from Object prototype - current[key] = current[key] || Object.create(null); - current = current[key]; - } - - const lastKey = keys[keys.length - 1]; - - // Final key assignment, ensuring no prototype pollution vulnerability - if ( - lastKey !== "__proto__" && - lastKey !== "constructor" && - lastKey !== "prototype" - ) { - current[lastKey] = value; - } -} diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index 15c676220fd..05f2dd8eecf 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -9,7 +9,8 @@ import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; -import { setNestedValueSafely } from "@/Utils/utils"; + +// import { setNestedValueSafely } from "@/Utils/utils"; const initialState = { changedFields: {}, @@ -91,10 +92,25 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { }); const handleValueChange = (value: any, name: string) => { - const changedFields = { ...state.changedFields }; + const keys = name.split("."); + // Validate keys to prevent prototype pollution - coderabbit suggested + if ( + keys.some((key) => + ["__proto__", "constructor", "prototype"].includes(key), + ) + ) { + return; + } - setNestedValueSafely(changedFields, name, value); + const changedFields = { ...state.changedFields }; + let current = changedFields; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key]) current[key] = {}; + current = current[key]; + } + current[keys[keys.length - 1]] = value; dispatch({ type: "set_changed_fields", changedFields }); }; diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index 903a3b8090d..1f6ed5dd929 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -5,7 +5,7 @@ import { ComboboxOption, ComboboxOptions, } from "@headlessui/react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -16,6 +16,8 @@ import { dropdownOptionClassNames, } from "@/components/Form/MultiSelectMenuV2"; +import useDebounce from "@/hooks/useDebounce"; + import { classNames } from "@/Utils/utils"; interface Props { @@ -68,32 +70,29 @@ const AutoCompleteAsync = (props: Props) => { const hasSelection = (!multiple && selected) || (multiple && selected?.length > 0); - const fetchDataAsync = useMemo(() => { - let timeoutId: ReturnType; - - return async (query: string) => { - clearTimeout(timeoutId); - setLoading(true); - - timeoutId = setTimeout(async () => { - const data = ((await fetchData(query)) || [])?.filter((d: any) => - filter ? filter(d) : true, - ); - - if (showNOptions !== undefined) { - setData(data.slice(0, showNOptions)); - } else { - setData(data); - } + const fetchDataDebounced = useDebounce(async (searchQuery: string) => { + setLoading(true); + try { + const fetchedData = (await fetchData(searchQuery)) || []; + const filteredData = filter + ? fetchedData.filter((item: any) => filter(item)) + : fetchedData; - setLoading(false); - }, debounceTime); - }; - }, [fetchData, showNOptions, debounceTime]); + setData( + showNOptions !== undefined + ? filteredData.slice(0, showNOptions) + : filteredData, + ); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }, debounceTime); useEffect(() => { - fetchDataAsync(query); - }, [query, fetchDataAsync]); + if (query) fetchDataDebounced(query); + }, [query, fetchDataDebounced]); return (
diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index b1cafd5018e..eb4ced4a0e0 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -14,6 +14,8 @@ import { formReducer, } from "@/components/Form/Utils"; +import useOmitBy from "@/hooks/useOmitBy"; + import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; import * as Notification from "@/Utils/Notifications"; import { classNames } from "@/Utils/utils"; @@ -43,6 +45,8 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); + const omitEmptyFields = useOmitBy(); + useEffect(() => { if (!asyncGetDefaults) return; @@ -57,11 +61,13 @@ const Form = ({ event.stopPropagation(); if (validate) { - const errors = Object.fromEntries( - Object.entries(validate(state.form)).filter( - ([_key, value]) => value !== "" && value !== undefined, - ), - ) as FormErrors; + // const errors = Object.fromEntries( + // Object.entries(validate(state.form)).filter( + // ([_key, value]) => value !== "" && value !== undefined, + // ), + // ) as FormErrors; + + const errors = omitEmptyFields(validate(state.form)) as FormErrors; if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index f133138591c..912a10cfde2 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -44,6 +44,11 @@ export default function DiagnosesFilter(props: Props) { prefetch: false, }); + const handleQuery = useDebounce( + (query: string) => refetch({ query: { query } }), + 300, + ); + useEffect(() => { if (res?.status === 500) { Error({ msg: "ICD-11 Diagnosis functionality is facing issues." }); @@ -90,10 +95,7 @@ export default function DiagnosesFilter(props: Props) { options={mergeQueryOptions(diagnoses, data ?? [], (obj) => obj.id)} optionLabel={(option) => option.label} optionValue={(option) => option} - onQuery={useDebounce( - (query: string) => refetch({ query: { query } }), - 300, - )} + onQuery={handleQuery} isLoading={loading} /> ); diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index 961a976d893..ac097005dcb 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; -export default function useDebounce( - callback: (...args: string[]) => void, +export default function useDebounce( + callback: (...args: T) => void, delay: number, ) { const callbackRef = useRef(callback); @@ -10,7 +10,7 @@ export default function useDebounce( }, [callback]); const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: string[]) => { + const debouncedCallback = (...args: T) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } diff --git a/src/hooks/useOmitBy.ts b/src/hooks/useOmitBy.ts new file mode 100644 index 00000000000..226f2d95a5e --- /dev/null +++ b/src/hooks/useOmitBy.ts @@ -0,0 +1,16 @@ +import { useCallback } from "react"; + +export default function useOmitBy() { + const omitEmptyFields = useCallback( + <_T>(obj: Record): Record => { + return Object.fromEntries( + Object.entries(obj).filter( + ([_key, value]) => value !== "" && value !== undefined, + ), + ); + }, + [], + ); + + return omitEmptyFields; +} From 25bd0c2b51d405779886f96c9a2497f91297d389 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sun, 1 Dec 2024 04:51:56 +0000 Subject: [PATCH 22/40] coderabbitai improvements --- src/components/Form/Form.tsx | 6 ------ src/hooks/useOmitBy.ts | 19 +++++++++---------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index eb4ced4a0e0..f918dcb39c5 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -61,12 +61,6 @@ const Form = ({ event.stopPropagation(); if (validate) { - // const errors = Object.fromEntries( - // Object.entries(validate(state.form)).filter( - // ([_key, value]) => value !== "" && value !== undefined, - // ), - // ) as FormErrors; - const errors = omitEmptyFields(validate(state.form)) as FormErrors; if (Object.keys(errors).length) { diff --git a/src/hooks/useOmitBy.ts b/src/hooks/useOmitBy.ts index 226f2d95a5e..946545ab083 100644 --- a/src/hooks/useOmitBy.ts +++ b/src/hooks/useOmitBy.ts @@ -1,16 +1,15 @@ import { useCallback } from "react"; -export default function useOmitBy() { - const omitEmptyFields = useCallback( - <_T>(obj: Record): Record => { +export default function useOmitBy>( + predicate: (value: unknown) => boolean = (value) => + value !== "" && value !== undefined && value !== null, +) { + return useCallback( + (obj: T): Partial => { return Object.fromEntries( - Object.entries(obj).filter( - ([_key, value]) => value !== "" && value !== undefined, - ), - ); + Object.entries(obj).filter(([_, value]) => predicate(value)), + ) as Partial; }, - [], + [predicate], ); - - return omitEmptyFields; } From 29a62e87ccc46e3f88c57451403385c19f7ffee9 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Sun, 1 Dec 2024 11:12:54 +0000 Subject: [PATCH 23/40] used omitBy as function instead of hook --- src/Utils/utils.ts | 11 +++++++++++ src/components/Form/Form.tsx | 8 ++------ src/hooks/useOmitBy.ts | 15 --------------- 3 files changed, 13 insertions(+), 21 deletions(-) delete mode 100644 src/hooks/useOmitBy.ts diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index e3c95c5cdaf..3a122ad3b9f 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -566,3 +566,14 @@ export const camelCase = (str: string): string => { .toLowerCase() .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")); }; + +//equivalent to lodash omitBy +export function omitBy>( + obj: T, + predicate: (value: unknown) => boolean = (value) => + value !== "" && value !== undefined && value !== null, +): Partial { + return Object.fromEntries( + Object.entries(obj).filter(([_, value]) => predicate(value)), + ) as Partial; +} diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index f918dcb39c5..f7b1be91c16 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -14,11 +14,9 @@ import { formReducer, } from "@/components/Form/Utils"; -import useOmitBy from "@/hooks/useOmitBy"; - import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; import * as Notification from "@/Utils/Notifications"; -import { classNames } from "@/Utils/utils"; +import { classNames, omitBy } from "@/Utils/utils"; type Props = { className?: string; @@ -45,8 +43,6 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); - const omitEmptyFields = useOmitBy(); - useEffect(() => { if (!asyncGetDefaults) return; @@ -61,7 +57,7 @@ const Form = ({ event.stopPropagation(); if (validate) { - const errors = omitEmptyFields(validate(state.form)) as FormErrors; + const errors = omitBy(validate(state.form)) as FormErrors; if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); diff --git a/src/hooks/useOmitBy.ts b/src/hooks/useOmitBy.ts deleted file mode 100644 index 946545ab083..00000000000 --- a/src/hooks/useOmitBy.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useCallback } from "react"; - -export default function useOmitBy>( - predicate: (value: unknown) => boolean = (value) => - value !== "" && value !== undefined && value !== null, -) { - return useCallback( - (obj: T): Partial => { - return Object.fromEntries( - Object.entries(obj).filter(([_, value]) => predicate(value)), - ) as Partial; - }, - [predicate], - ); -} From 7022c15d69f1f6481788297b624ae778e016d516 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 2 Dec 2024 17:57:17 +0000 Subject: [PATCH 24/40] separated startCase into cleanString and capitalizeWords --- src/Utils/Notifications.js | 4 ++-- src/Utils/utils.ts | 14 ++++++++------ src/components/Patient/SampleDetails.tsx | 10 +++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 9a9329941ad..20b386eaca0 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,7 +1,7 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { camelCase, startCase } from "@/Utils/utils"; +import { camelCase, cleanString } from "@/Utils/utils"; defaultModules.set(PNotifyMobile, {}); @@ -45,7 +45,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = startCase(camelCase(key)); + let keyName = cleanString(camelCase(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 3a122ad3b9f..6819a33406c 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -545,14 +545,16 @@ export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; -// Capitalizes the first letter of each word in a string, handling edge cases -export const startCase = (str: string): string => { +//removes all symbols except _ and - +export const cleanString = (str: string): string => { + if (!str || str.length === 0) return ""; + return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); +}; + +// just capitalizes (as per startCase working) +export const capitalizeWords = (str: string): string => { if (!str || str.length === 0) return ""; return str - .replace(/([a-z])([A-Z])/g, "$1 $2") - .replace(/[_-]+/g, " ") - .replace(/\s+/g, " ") - .trim() .split(" ") .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(" "); diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index b6d1805a9b0..3c673b786bb 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { camelCase, startCase } from "@/Utils/utils"; +import { camelCase, capitalizeWords, cleanString } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -372,7 +372,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {startCase(camelCase(sampleDetails.doctor_name))} + {capitalizeWords( + cleanString(camelCase(sampleDetails.doctor_name)), + )}
)} @@ -456,7 +458,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {startCase(camelCase(sampleDetails.sample_type))} + {capitalizeWords( + cleanString(camelCase(sampleDetails.sample_type)), + )} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( From e4cab8835b600a507be23fc86529564c6f47f15d Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 2 Dec 2024 18:27:25 +0000 Subject: [PATCH 25/40] fixing --- src/components/Patient/SampleDetails.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 3c673b786bb..0fe589ec4f2 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { camelCase, capitalizeWords, cleanString } from "@/Utils/utils"; +import { capitalizeWords, cleanString } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -372,9 +372,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {capitalizeWords( - cleanString(camelCase(sampleDetails.doctor_name)), - )} + {capitalizeWords(cleanString(sampleDetails.doctor_name))} )} @@ -458,9 +456,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {capitalizeWords( - cleanString(camelCase(sampleDetails.sample_type)), - )} + {capitalizeWords(cleanString(sampleDetails.sample_type))} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( From 276afa090857c7bbc331d90ee23b158ea93e2816 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 2 Dec 2024 18:45:26 +0000 Subject: [PATCH 26/40] fixing --- src/Utils/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 6819a33406c..b3fce052ba0 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -545,13 +545,13 @@ export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; -//removes all symbols except _ and - +//removes all symbols except _ and - (part of startCase) export const cleanString = (str: string): string => { if (!str || str.length === 0) return ""; return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); }; -// just capitalizes (as per startCase working) +// just capitalizes (as per startCase working) (part of startCase) export const capitalizeWords = (str: string): string => { if (!str || str.length === 0) return ""; return str From 6ebc815f707b85de50386935f5c7fe186f47987a Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 2 Dec 2024 19:14:30 +0000 Subject: [PATCH 27/40] fixing --- src/Utils/Notifications.js | 4 ++-- src/components/Patient/SampleDetails.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 20b386eaca0..856917080eb 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,7 +1,7 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { camelCase, cleanString } from "@/Utils/utils"; +import { capitalizeWords, cleanString } from "@/Utils/utils"; defaultModules.set(PNotifyMobile, {}); @@ -45,7 +45,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = cleanString(camelCase(key)); + let keyName = capitalizeWords(cleanString(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 0fe589ec4f2..3c673b786bb 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { capitalizeWords, cleanString } from "@/Utils/utils"; +import { camelCase, capitalizeWords, cleanString } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -372,7 +372,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {capitalizeWords(cleanString(sampleDetails.doctor_name))} + {capitalizeWords( + cleanString(camelCase(sampleDetails.doctor_name)), + )} )} @@ -456,7 +458,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {capitalizeWords(cleanString(sampleDetails.sample_type))} + {capitalizeWords( + cleanString(camelCase(sampleDetails.sample_type)), + )} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( From 399b7b5396e0e814c0247318c121235c72df0e35 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Mon, 2 Dec 2024 19:30:40 +0000 Subject: [PATCH 28/40] removed camelCase --- src/Utils/utils.ts | 9 --------- src/components/Patient/SampleDetails.tsx | 10 +++------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index b3fce052ba0..4e04d24263b 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -560,15 +560,6 @@ export const capitalizeWords = (str: string): string => { .join(" "); }; -// Converts a string to camelCase format, first word to lowercase and each subsequent word - uppercase letter, with no spaces. -export const camelCase = (str: string): string => { - if (!str || str.length === 0) return ""; - return str - .trim() - .toLowerCase() - .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")); -}; - //equivalent to lodash omitBy export function omitBy>( obj: T, diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 3c673b786bb..0fe589ec4f2 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,7 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { camelCase, capitalizeWords, cleanString } from "@/Utils/utils"; +import { capitalizeWords, cleanString } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -372,9 +372,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {capitalizeWords( - cleanString(camelCase(sampleDetails.doctor_name)), - )} + {capitalizeWords(cleanString(sampleDetails.doctor_name))} )} @@ -458,9 +456,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {capitalizeWords( - cleanString(camelCase(sampleDetails.sample_type)), - )} + {capitalizeWords(cleanString(sampleDetails.sample_type))} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( From 3e5127287dcfe982e8d743cd92a28758482270e7 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Tue, 3 Dec 2024 07:22:18 +0000 Subject: [PATCH 29/40] cypress test 2 fix --- src/components/Form/AutoCompleteAsync.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index 1f6ed5dd929..48c36258765 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -91,7 +91,7 @@ const AutoCompleteAsync = (props: Props) => { }, debounceTime); useEffect(() => { - if (query) fetchDataDebounced(query); + fetchDataDebounced(query); }, [query, fetchDataDebounced]); return ( From 8dd3a0473e4554f8ac02c017f949fda7d6b9028e Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Tue, 3 Dec 2024 08:05:35 +0000 Subject: [PATCH 30/40] fixing --- src/components/Patient/SampleDetails.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 0fe589ec4f2..1d0ce2f302a 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -372,7 +372,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {capitalizeWords(cleanString(sampleDetails.doctor_name))} + {capitalizeWords(cleanString(sampleDetails.doctor_name || ""))} )} @@ -456,7 +456,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {capitalizeWords(cleanString(sampleDetails.sample_type))} + {capitalizeWords(cleanString(sampleDetails.sample_type || ""))} )} {sampleDetails?.sample_type === "OTHER TYPE" && ( From 0a1354756d5dbc345a5e75419c69db920798b086 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Tue, 3 Dec 2024 11:19:16 +0000 Subject: [PATCH 31/40] debounce error --- src/components/Form/AutoCompleteAsync.tsx | 12 +++++++++--- src/hooks/useDebounce.ts | 9 ++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index 48c36258765..9eafae47e71 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -78,11 +78,17 @@ const AutoCompleteAsync = (props: Props) => { ? fetchedData.filter((item: any) => filter(item)) : fetchedData; - setData( + const newData = showNOptions !== undefined ? filteredData.slice(0, showNOptions) - : filteredData, - ); + : filteredData; + + setData((prevData) => { + if (JSON.stringify(prevData) !== JSON.stringify(newData)) { + return newData; + } + return prevData; + }); } catch (error) { console.error("Error fetching data:", error); } finally { diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index ac097005dcb..66ee398f962 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -5,11 +5,18 @@ export default function useDebounce( delay: number, ) { const callbackRef = useRef(callback); + const timeoutRef = useRef | null>(null); + useEffect(() => { callbackRef.current = callback; }, [callback]); - const timeoutRef = useRef | null>(null); + useEffect(() => { + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }; + }, []); + const debouncedCallback = (...args: T) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); From 4d7237007a1429cf290f556dee22613b08c43b21 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Tue, 3 Dec 2024 19:26:15 +0000 Subject: [PATCH 32/40] import error fix --- src/Utils/Notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 856917080eb..2a9c5ab6ff6 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,7 +1,7 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { capitalizeWords, cleanString } from "@/Utils/utils"; +import { cleanString } from "@/Utils/utils"; defaultModules.set(PNotifyMobile, {}); @@ -45,7 +45,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = capitalizeWords(cleanString(key)); + let keyName = cleanString(key); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; From 3c08cb6bb2eccd86beb2ef7f3bb71c13c7545ff7 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Tue, 3 Dec 2024 20:06:42 +0000 Subject: [PATCH 33/40] fix --- src/Utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 173e386d2bf..1a8d834758c 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -545,7 +545,7 @@ export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; -//removes all symbols except _ and - (part of startCase) +//removes all symbols except _ and - (one part of startCase) export const cleanString = (str: string): string => { if (!str || str.length === 0) return ""; return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); From 7314d601a4181cf3021568f8fff25c02685a67a7 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 4 Dec 2024 09:48:07 +0000 Subject: [PATCH 34/40] fix --- src/Utils/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 1a8d834758c..fc91e01580b 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -547,7 +547,6 @@ export const keysOf = (obj: T) => { //removes all symbols except _ and - (one part of startCase) export const cleanString = (str: string): string => { - if (!str || str.length === 0) return ""; return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); }; From 063ee7f7f54b19bfded89911c618ad580a7ec440 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 4 Dec 2024 14:42:29 +0000 Subject: [PATCH 35/40] fixing tests --- src/Utils/utils.ts | 6 +++--- src/components/Form/AutoCompleteAsync.tsx | 12 +++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index fc91e01580b..9c19a62dc36 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -550,9 +550,9 @@ export const cleanString = (str: string): string => { return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); }; -//equivalent to lodash omitBy +// Utility to check if a value is "empty" export const isEmpty = (value: unknown) => { - return value !== "" && value != undefined; + return value === "" || value == undefined; }; // equivalent to lodash omitBy @@ -561,6 +561,6 @@ export function omitBy>( predicate: (value: unknown) => boolean = isEmpty, ): Partial { return Object.fromEntries( - Object.entries(obj).filter(([_, value]) => predicate(value)), + Object.entries(obj).filter(([_, value]) => !predicate(value)), ) as Partial; } diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index 9eafae47e71..48c36258765 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -78,17 +78,11 @@ const AutoCompleteAsync = (props: Props) => { ? fetchedData.filter((item: any) => filter(item)) : fetchedData; - const newData = + setData( showNOptions !== undefined ? filteredData.slice(0, showNOptions) - : filteredData; - - setData((prevData) => { - if (JSON.stringify(prevData) !== JSON.stringify(newData)) { - return newData; - } - return prevData; - }); + : filteredData, + ); } catch (error) { console.error("Error fetching data:", error); } finally { From fa6cda7be435464334c05a73025ff8f42d8c279d Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 4 Dec 2024 15:34:46 +0000 Subject: [PATCH 36/40] utils --- src/Utils/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 2712ede615f..eb057ebbc73 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -556,10 +556,13 @@ export const cleanStringForNotifications = (str: string): string => { // just capitalizes (as per startCase working) (part of startCase) export const capitalizeWords = (str: string): string => { - if (!str || str.length === 0) return ""; + if (!str) return ""; return str .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .map( + (word) => + word.charAt(0).toLocaleUpperCase() + word.slice(1).toLocaleLowerCase(), + ) .join(" "); }; From 4122b418057540122a1176a0188a19019203db24 Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Wed, 4 Dec 2024 18:38:52 +0000 Subject: [PATCH 37/40] cypress test 1 --- src/Utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index eb057ebbc73..8e4918cb6d6 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -554,7 +554,7 @@ export const cleanStringForNotifications = (str: string): string => { return str.replace(/[^a-zA-Z0-9]+/g, " ").trim(); }; -// just capitalizes (as per startCase working) (part of startCase) +// just capitalizes (as per startCase working) (part of startCase) (only inside Notification.js) export const capitalizeWords = (str: string): string => { if (!str) return ""; return str From 8feb118cd7911836ac7349cbeed6d1499ebaa31e Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 5 Dec 2024 16:59:13 +0530 Subject: [PATCH 38/40] remove unnecessary prop propogation --- src/components/Form/FormFields/TextFormField.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Form/FormFields/TextFormField.tsx b/src/components/Form/FormFields/TextFormField.tsx index 5517100e9ac..fd948e0a264 100644 --- a/src/components/Form/FormFields/TextFormField.tsx +++ b/src/components/Form/FormFields/TextFormField.tsx @@ -101,7 +101,6 @@ const TextFormField = forwardRef((props: TextFormFieldProps, ref) => { name={field.name} value={field.value} required={field.required} - autoCapitalize={props.autoCapitalize} onChange={(e) => field.handleChange(e.target.value)} /> {props.clearable && field.value && ( From 4d39aa10602a24f66857ff76f77d9f73e160f3e1 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 5 Dec 2024 17:00:12 +0530 Subject: [PATCH 39/40] discourage using capitalizeWords fn. by being kept as a shared utility fn. use tailwindcss's alternative --- src/Utils/Notifications.js | 23 ++++++++++++++++++++--- src/Utils/utils.ts | 23 +---------------------- src/components/Form/Form.tsx | 4 ++-- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 4044cec3927..08bc90443b5 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,8 +1,6 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { capitalizeWords, cleanStringForNotifications } from "@/Utils/utils"; - defaultModules.set(PNotifyMobile, {}); const notifyStack = new Stack({ @@ -36,6 +34,25 @@ const notify = (text, type) => { }); }; +/** + * Formats input string to a more human readable format + * @param {string} key - The key to format + * @returns {string} The formatted key + * @example + * formatKey("patient_name") => "Patient Name" + */ +const formatKey = (key) => { + return key + .replace(/[^a-zA-Z0-9]+/g, " ") // Replace non-alphanumeric characters with a space + .trim() + .split(" ") + .map( + (word) => + word.charAt(0).toLocaleUpperCase() + word.slice(1).toLocaleLowerCase(), + ) // Capitalize the first letter of each word and lowercase the rest + .join(" "); +}; + const notifyError = (error) => { let errorMsg = ""; if (typeof error === "string" || !error) { @@ -45,7 +62,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = capitalizeWords(cleanStringForNotifications(key)); + let keyName = formatKey(key); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 8e4918cb6d6..a7e6a0a78ec 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -545,27 +545,6 @@ export const keysOf = (obj: T) => { return Object.keys(obj) as (keyof T)[]; }; -//removes all symbols except _ and - (one part of startCase) -export const cleanStringForNames = (str: string): string => { - return str.replace(/[^a-zA-Z0-9_-]+/g, " ").trim(); -}; - -export const cleanStringForNotifications = (str: string): string => { - return str.replace(/[^a-zA-Z0-9]+/g, " ").trim(); -}; - -// just capitalizes (as per startCase working) (part of startCase) (only inside Notification.js) -export const capitalizeWords = (str: string): string => { - if (!str) return ""; - return str - .split(" ") - .map( - (word) => - word.charAt(0).toLocaleUpperCase() + word.slice(1).toLocaleLowerCase(), - ) - .join(" "); -}; - // Utility to check if a value is "empty" export const isEmpty = (value: unknown) => { return value === "" || value == undefined; @@ -574,7 +553,7 @@ export const isEmpty = (value: unknown) => { // equivalent to lodash omitBy export function omitBy>( obj: T, - predicate: (value: unknown) => boolean = isEmpty, + predicate: (value: unknown) => boolean, ): Partial { return Object.fromEntries( Object.entries(obj).filter(([_, value]) => !predicate(value)), diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index f7b1be91c16..63b8c3614e5 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -16,7 +16,7 @@ import { import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; import * as Notification from "@/Utils/Notifications"; -import { classNames, omitBy } from "@/Utils/utils"; +import { classNames, isEmpty, omitBy } from "@/Utils/utils"; type Props = { className?: string; @@ -57,7 +57,7 @@ const Form = ({ event.stopPropagation(); if (validate) { - const errors = omitBy(validate(state.form)) as FormErrors; + const errors = omitBy(validate(state.form), isEmpty) as FormErrors; if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); From 3d03d7996d89d60c5c2c9bccd074b02a9bdeefb6 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 5 Dec 2024 17:03:49 +0530 Subject: [PATCH 40/40] remove unnecessary usages --- src/components/Patient/SampleDetails.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 24ceb3c2b3a..40958cb299b 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -14,7 +14,6 @@ import { GENDER_TYPES, TEST_TYPE_CHOICES } from "@/common/constants"; import { DetailRoute } from "@/Routers/types"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { cleanStringForNames } from "@/Utils/utils"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { @@ -372,7 +371,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("doctors_name")}:{" "} - {cleanStringForNames(sampleDetails.doctor_name || "")} + {sampleDetails.doctor_name} )} @@ -457,7 +456,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {t("sample_type")}:{" "} - {cleanStringForNames(sampleDetails.sample_type || "")} + {sampleDetails.sample_type} )}