diff --git a/src/Providers/PatientUserProvider.tsx b/src/Providers/PatientUserProvider.tsx index 1ae9a9af8f1..2f85b8dfeab 100644 --- a/src/Providers/PatientUserProvider.tsx +++ b/src/Providers/PatientUserProvider.tsx @@ -50,7 +50,9 @@ export default function PatientUserProvider({ children }: Props) { const selectedPatient = userData.results.find((patient) => patient.id === localPatient?.id) || userData.results[0]; - setSelectedPatient(selectedPatient); + if (selectedPatient) { + setSelectedPatient(selectedPatient); + } localStorage.setItem("selectedPatient", JSON.stringify(selectedPatient)); } }, [userData]); diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index ed8ed8a8aa0..d5b3b906f8f 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -65,7 +65,8 @@ const Routes: AppRoutes = { ...(import.meta.env.PROD ? { "/icons": () => } : {}), "/apps": () => , - "/apps/plug-configs/:slug": ({ slug }) => , + "/apps/plug-configs/:slug": ({ slug }) => + slug ? : , "/login": () => , }; diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index 6ae459d7f7a..76711378a0b 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -10,15 +10,20 @@ const consultationRoutes: AppRoutes = { "/facility/:facilityId/encounter/:encounterId/prescriptions/print": ({ facilityId, encounterId, - }) => , + }) => ( + + ), "/facility/:facilityId/encounter/:encounterId/:tab": ({ facilityId, encounterId, tab, }) => ( ), @@ -27,18 +32,18 @@ const consultationRoutes: AppRoutes = { patientId, }) => ( ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/edit_encounter": ({ facilityId, encounterId, patientId }) => ( ), "/facility/:facilityId/patient/:patientId/questionnaire": ({ @@ -46,31 +51,34 @@ const consultationRoutes: AppRoutes = { patientId, }) => ( ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire": ({ facilityId, encounterId, patientId }) => ( ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire/:slug": ({ facilityId, encounterId, slug, patientId }) => ( ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire_response/:id": ({ patientId, id }) => ( - + ), "/facility/:facilityId/patient/:patientId/encounterId/:id/files/": ({ facilityId, @@ -78,8 +86,8 @@ const consultationRoutes: AppRoutes = { id, }) => ( diff --git a/src/Routers/routes/FacilityRoutes.tsx b/src/Routers/routes/FacilityRoutes.tsx index 44637fdbcf8..39b030186fe 100644 --- a/src/Routers/routes/FacilityRoutes.tsx +++ b/src/Routers/routes/FacilityRoutes.tsx @@ -17,22 +17,28 @@ const FacilityRoutes: AppRoutes = { ), "/facility/:facilityId": ({ facilityId }) => ( - + ), "/facility/:facilityId/users": ({ facilityId }) => ( - + ), "/facility/:facilityId/resource/new": ({ facilityId }) => ( - + ), "/facility/:facilityId/organization": ({ facilityId }) => ( - + ), "/facility/:facilityId/organization/:id": ({ facilityId, id }) => ( - + ), "/facility/:facilityId/organization/:id/users": ({ facilityId, id }) => ( - + ), }; diff --git a/src/Routers/routes/OrganizationRoutes.tsx b/src/Routers/routes/OrganizationRoutes.tsx index d6e83cb4f0f..cc7c3fb822c 100644 --- a/src/Routers/routes/OrganizationRoutes.tsx +++ b/src/Routers/routes/OrganizationRoutes.tsx @@ -7,29 +7,49 @@ import OrganizationView from "@/pages/Organization/OrganizationView"; const OrganizationRoutes: AppRoutes = { "/organization": () => , - "/organization/:id": ({ id }) => , - "/organization/:id/users": ({ id }) => , - "/organization/:id/patients": ({ id }) => , + "/organization/:id": ({ id }) => , + "/organization/:id/users": ({ id }) => , + "/organization/:id/patients": ({ id }) => ( + + ), "/organization/:id/facilities": ({ id }) => ( - + ), "/organization/:navOrganizationId/children/:id": ({ navOrganizationId, id, - }) => , + }) => ( + + ), "/organization/:navOrganizationId/children/:id/users": ({ navOrganizationId, id, - }) => , + }) => ( + + ), "/organization/:navOrganizationId/children/:id/patients": ({ navOrganizationId, id, - }) => , + }) => ( + + ), "/organization/:navOrganizationId/children/:id/facilities": ({ navOrganizationId, id, }) => ( - + ), }; diff --git a/src/Routers/routes/PatientRoutes.tsx b/src/Routers/routes/PatientRoutes.tsx index 77ede2c44a9..57aefb55086 100644 --- a/src/Routers/routes/PatientRoutes.tsx +++ b/src/Routers/routes/PatientRoutes.tsx @@ -10,38 +10,40 @@ import VerifyPatient from "@/pages/Patients/VerifyPatient"; const PatientRoutes: AppRoutes = { "/facility/:facilityId/patients": ({ facilityId }) => ( - + ), "/facility/:facilityId/encounters": ({ facilityId }) => ( ), "/facility/:facilityId/patients/verify": ({ facilityId }) => ( - + ), - "/patient/:id": ({ id }) => , + "/patient/:id": ({ id }) => , "/facility/:facilityId/patient/create": ({ facilityId }) => ( - + ), "/facility/:facilityId/patient/:id": ({ facilityId, id }) => ( - + ), ...patientTabs.reduce((acc: AppRoutes, tab) => { acc["/facility/:facilityId/patient/:id/" + tab.route] = ({ facilityId, id, - }) => ; + }) => ( + + ); return acc; }, {}), "/facility/:facilityId/patient/:id/update": ({ facilityId, id }) => ( - + ), "/facility/:facilityId/patient/:patientId/files": ({ facilityId, patientId, }) => ( ), diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx index 547aeb53610..3b5ec42901f 100644 --- a/src/Routers/routes/ResourceRoutes.tsx +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -14,8 +14,8 @@ const ResourceRoutes: AppRoutes = { "/resource": () => , "/resource/board": () => , "/resource/list": () => , - "/resource/:id": ({ id }) => , - "/resource/:id/update": ({ id }) => , + "/resource/:id": ({ id }) => , + "/resource/:id/update": ({ id }) => , }; export default ResourceRoutes; diff --git a/src/Routers/routes/ScheduleRoutes.tsx b/src/Routers/routes/ScheduleRoutes.tsx index 4410009a331..5773fce383a 100644 --- a/src/Routers/routes/ScheduleRoutes.tsx +++ b/src/Routers/routes/ScheduleRoutes.tsx @@ -24,13 +24,21 @@ const ScheduleRoutes: AppRoutes = { "/facility/:facilityId/patient/:patientId/book-appointment": ({ facilityId, patientId, - }) => , + }) => ( + + ), "/facility/:facilityId/patient/:patientId/appointments/:appointmentId": ({ facilityId, appointmentId, }) => ( - + ), }; diff --git a/src/Routers/routes/UserRoutes.tsx b/src/Routers/routes/UserRoutes.tsx index 393b5fb9b2a..7b7923c753d 100644 --- a/src/Routers/routes/UserRoutes.tsx +++ b/src/Routers/routes/UserRoutes.tsx @@ -10,14 +10,16 @@ const UserRoutes: AppRoutes = { facilityId, username, tab, - }) => , + }) => ( + + ), "/users/:username": ({ username }) => ( ), "/users/:username/:tab": ({ username, tab }) => ( - + ), - "/user/:tab": ({ tab }) => , + "/user/:tab": ({ tab }) => , }; export default UserRoutes; diff --git a/src/Routers/routes/questionnaireRoutes.tsx b/src/Routers/routes/questionnaireRoutes.tsx index 044f9fcc1d4..d11557f0e70 100644 --- a/src/Routers/routes/questionnaireRoutes.tsx +++ b/src/Routers/routes/questionnaireRoutes.tsx @@ -6,8 +6,8 @@ import { AppRoutes } from "../AppRouter"; const QuestionnaireRoutes: AppRoutes = { "/questionnaire": () => , - "/questionnaire/:id": ({ id }) => , - "/questionnaire/:id/edit": ({ id }) => , + "/questionnaire/:id": ({ id }) => , + "/questionnaire/:id/edit": ({ id }) => , }; export default QuestionnaireRoutes; diff --git a/src/Utils/AutoSave.tsx b/src/Utils/AutoSave.tsx index f621a58802d..0ba6c012034 100644 --- a/src/Utils/AutoSave.tsx +++ b/src/Utils/AutoSave.tsx @@ -113,7 +113,7 @@ export function DraftSection(props: { const saveKey = useRef(`form_draft_${window.location.pathname}`); const draftStarted = drafts.length > 0 - ? drafts[drafts.length - 1].draft == props.formData + ? (drafts[drafts.length - 1]?.draft ?? {}) == props.formData : false; useEffect(() => { @@ -174,27 +174,19 @@ export const RestoreDraftButton = () => { return null; } + const draft = draftStarted ? drafts[0] : drafts[drafts.length - 1]; + return ( ); diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 5b6af0ac5a6..e8ea5288578 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -171,12 +171,11 @@ export const formatPhoneNumber = (phoneNumber: string) => { const countryCode = getCountryCode(phoneNumber); if (!countryCode) return phoneNumber; const phoneCodes: Record = phoneCodesJson; - return ( - "+" + - phoneCodes[countryCode].code + - " " + - phoneNumber.slice(phoneCodes[countryCode].code.length + 1) - ); + const countryData = phoneCodes[countryCode]; + + if (!countryData) return phoneNumber; + + return `+${countryData.code} ${phoneNumber.slice(countryData.code.length + 1)}`; } return phoneNumber; }; @@ -188,14 +187,18 @@ export const getCountryCode = (phoneNumber: string) => { phoneNumber = phoneNumber.slice(1); const allMatchedCountries: { name: string; code: string }[] = []; for (let i = 0; i < phoneCodesArr.length; i++) { - if ( - phoneNumber.startsWith( - phoneCodes[phoneCodesArr[i]].code.replaceAll("-", ""), - ) - ) { + const countryCode = phoneCodesArr[i]; + if (!countryCode) continue; + + const countryData = phoneCodes[countryCode]; + if (!countryData || !countryData.code) continue; + + const formattedCode = countryData.code.replaceAll("-", ""); + + if (phoneNumber.startsWith(formattedCode)) { allMatchedCountries.push({ - name: phoneCodesArr[i], - code: phoneCodes[phoneCodesArr[i]].code.replaceAll("-", ""), + name: countryCode, + code: formattedCode, }); } } diff --git a/src/common/schemaParser.ts b/src/common/schemaParser.ts index d4c2d89e1ee..dacd7c5c337 100644 --- a/src/common/schemaParser.ts +++ b/src/common/schemaParser.ts @@ -91,16 +91,16 @@ const parseDataWithSchema = ( const parsedData: ParsedData[] = []; const dataWithErrors: DataWithError[] = data.map((item, index) => { return Object.keys(schema).reduce((acc, key) => { - const { - [key]: { value, error }, - } = validateAndParse(key, item[key], schema[key]); - const parsedRow = { [schema[key].prop]: value }; + const { value, error } = + validateAndParse(key, item[key], schema[key] as SingleKeySchema)[key] ?? + {}; + const parsedRow = { [schema[key]?.prop ?? key]: value }; if (error) { errors.push({ index, key, error }); } - const prop = schema[key].prop || key; + const prop = schema[key]?.prop || key; - if (schema[key].parent) { + if (schema[key]?.parent) { const indexKey = schema[key].parent || key; acc[indexKey] = acc[indexKey] || {}; acc[indexKey][prop] = { value, error }; @@ -183,7 +183,10 @@ const schemaParser = ( ); const ParsedDataWithOutErrors = parsedData.filter((item, index) => { - return !Object.values(dataWithErrors[index]).some((item) => item.error); + return ( + dataWithErrors[index] && + !Object.values(dataWithErrors[index]).some((item) => item.error) + ); }); return { diff --git a/src/components/Common/AudioPlayer.tsx b/src/components/Common/AudioPlayer.tsx index 9f4545d69ab..de2e000618d 100644 --- a/src/components/Common/AudioPlayer.tsx +++ b/src/components/Common/AudioPlayer.tsx @@ -91,8 +91,10 @@ function AudioPlayer({ src, className }: AudioPlayerProps) { const handleSliderChange = useCallback((value: number[]) => { if (audioRef.current) { - audioRef.current.currentTime = value[0]; - setCurrentTime(value[0]); + if (value[0] !== undefined) { + audioRef.current.currentTime = value[0]; + setCurrentTime(value[0]); + } } }, []); diff --git a/src/components/Common/Avatar.tsx b/src/components/Common/Avatar.tsx index 0ef1ce7ad56..728fa6a37fd 100644 --- a/src/components/Common/Avatar.tsx +++ b/src/components/Common/Avatar.tsx @@ -30,7 +30,7 @@ const stringToInt = (name: string): number => { const toColor = (name: string): [string, string] => { const index = stringToInt(name) % colors.length; - const backgroundColor = colors[index]; + const backgroundColor = colors[index] ?? "#FFFFFF"; return [backgroundColor, "#333333"]; // Using dark gray for text }; @@ -58,7 +58,7 @@ const Avatar: React.FC = ({ className, icon, }) => { - const [bgColor] = propColors || (name ? toColor(name) : toColor("")); + const [bgColor] = propColors || toColor(name || ""); return (
- {observations[0].details.enteredBy} + {observations[0]?.details.enteredBy}
diff --git a/src/components/Common/DateTextInput.tsx b/src/components/Common/DateTextInput.tsx index f2cd4844a1c..93b84e846f3 100644 --- a/src/components/Common/DateTextInput.tsx +++ b/src/components/Common/DateTextInput.tsx @@ -62,7 +62,8 @@ export default function DateTextInput(props: { const getBlurredValue = (rawValue: string, key: string) => { const maxMap = [31, 12, 2999, 23, 59]; const index = Object.keys(editingText).findIndex((et) => et === key); - const value = Math.min(maxMap[index], parseInt(rawValue)); + const maxValue = maxMap[index]; + const value = maxValue && Math.min(maxValue, parseInt(rawValue)); const finalValue = rawValue.trim() !== "" ? ("000" + value).slice(key === "year" ? -4 : -2) diff --git a/src/components/Common/ExcelFIleDragAndDrop.tsx b/src/components/Common/ExcelFIleDragAndDrop.tsx index 06e99e9df5f..d4b6fd26f63 100644 --- a/src/components/Common/ExcelFIleDragAndDrop.tsx +++ b/src/components/Common/ExcelFIleDragAndDrop.tsx @@ -63,8 +63,10 @@ export default function ExcelFileDragAndDrop({ cellDates: true, }); const worksheetName = workbook.SheetNames[0]; - const worksheet = workbook.Sheets[worksheetName]; - const data = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); + const worksheet = worksheetName && workbook.Sheets[worksheetName]; + const data = worksheet + ? XLSX.utils.sheet_to_json(worksheet, { defval: "" }) + : []; //converts the date to string data.forEach((row: any) => { Object.keys(row).forEach((key) => { @@ -119,12 +121,14 @@ export default function ExcelFileDragAndDrop({ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ]; - if (!fileTypes.includes(droppedFile?.type)) { + if (!fileTypes.includes(droppedFile?.type ?? "")) { dragProps.setFileDropError("Please drop a Excel / CSV file to upload!"); setSelectedFile(null); return; } - onSelectFile(droppedFile); + if (droppedFile) { + onSelectFile(droppedFile); + } }; return ( @@ -179,7 +183,9 @@ export default function ExcelFileDragAndDrop({ onChange={(e) => { const files = e.target.files; if (files && files.length > 0) { - onSelectFile(files[0]); + if (files[0]) { + onSelectFile(files[0]); + } } }} className="hidden" diff --git a/src/components/Common/GLocationPicker.tsx b/src/components/Common/GLocationPicker.tsx index cee05ce4245..0f55ebafa00 100644 --- a/src/components/Common/GLocationPicker.tsx +++ b/src/components/Common/GLocationPicker.tsx @@ -137,7 +137,7 @@ const Map: React.FC = ({ React.useEffect(() => { if (searchRef.current && map && !searchBox) { - map.controls[google.maps.ControlPosition.TOP_CENTER].push( + map.controls[google.maps.ControlPosition.TOP_CENTER]?.push( searchRef.current, ); @@ -156,7 +156,7 @@ const Map: React.FC = ({ handleOnChange && places && places.length > 0 && - places[0].geometry?.location + places[0]?.geometry?.location ) { const selectedLocation = places[0].geometry.location; handleOnChange(selectedLocation); @@ -168,7 +168,7 @@ const Map: React.FC = ({ React.useEffect(() => { if (mapCloseRef.current && map) { - map.controls[google.maps.ControlPosition.TOP_RIGHT].push( + map.controls[google.maps.ControlPosition.TOP_RIGHT]?.push( mapCloseRef.current, ); } @@ -176,7 +176,7 @@ const Map: React.FC = ({ React.useEffect(() => { if (currentLocationSelectRef.current && map) { - map.controls[google.maps.ControlPosition.TOP_LEFT].push( + map.controls[google.maps.ControlPosition.TOP_LEFT]?.push( currentLocationSelectRef.current, ); } diff --git a/src/components/Common/SearchByMultipleFields.tsx b/src/components/Common/SearchByMultipleFields.tsx index ffc3822fea7..5e297355254 100644 --- a/src/components/Common/SearchByMultipleFields.tsx +++ b/src/components/Common/SearchByMultipleFields.tsx @@ -69,15 +69,15 @@ const SearchByMultipleFields: React.FC = ({ initialOptionIndex || 0, ); const selectedOption = options[selectedOptionIndex]; - const [searchValue, setSearchValue] = useState(selectedOption.value || ""); + const [searchValue, setSearchValue] = useState(selectedOption?.value || ""); const [open, setOpen] = useState(false); const inputRef = useRef(null); const [focusedIndex, setFocusedIndex] = useState(0); const [error, setError] = useState(); useEffect(() => { - if (!(selectedOption.type === "phone" && searchValue.length < 13)) { - setSearchValue(options[selectedOptionIndex].value); + if (!(selectedOption?.type === "phone" && searchValue.length < 13)) { + setSearchValue(options[selectedOptionIndex]?.value ?? ""); } }, [options]); @@ -95,13 +95,15 @@ const SearchByMultipleFields: React.FC = ({ (index: number) => { setSelectedOptionIndex(index); const option = options[index]; - setSearchValue(option.value || ""); - setFocusedIndex(options.findIndex((op) => op.key === option.key)); + setFocusedIndex(options.findIndex((op) => op.key === option?.key)); setOpen(false); inputRef.current?.focus(); setError(false); - onSearch(option.key, option.value); - onFieldChange?.(options[index]); + if (option) { + setSearchValue(option.value); + onSearch(option.key, option.value); + onFieldChange?.(option); + } }, [onSearch], ); @@ -157,10 +159,10 @@ const SearchByMultipleFields: React.FC = ({ }, [selectedOptionIndex]); useEffect(() => { - if (selectedOption.value !== searchValue) { - onSearch(selectedOption.key, searchValue); + if (selectedOption?.value !== searchValue) { + onSearch(selectedOption?.key ?? "", searchValue); } - }, [searchValue, selectedOption.key, selectedOption.value, onSearch]); + }, [searchValue, selectedOption?.key, selectedOption?.value, onSearch]); const handleSearchChange = useCallback((event: EventType) => { const value = "target" in event ? event.target.value : event.value; @@ -178,12 +180,12 @@ const SearchByMultipleFields: React.FC = ({ ), } as const; - switch (selectedOption.type) { + switch (selectedOption?.type) { case "phone": return ( = ({ ); @@ -271,7 +273,7 @@ const SearchByMultipleFields: React.FC = ({ size="xs" data-test-id={id + "__" + option.key} className={cn( - selectedOption.key === option.key + selectedOption?.key === option.key ? "bg-primary-100 text-primary-700 hover:bg-primary-200 border-primary-400" : "bg-gray-100 text-gray-700 hover:bg-gray-200", buttonClassName, diff --git a/src/components/Facility/DuplicatePatientDialog.tsx b/src/components/Facility/DuplicatePatientDialog.tsx index ea12f1d06c4..03bc3538815 100644 --- a/src/components/Facility/DuplicatePatientDialog.tsx +++ b/src/components/Facility/DuplicatePatientDialog.tsx @@ -39,7 +39,7 @@ const DuplicatePatientDialog = (props: Props) => {

{t("patient_records_found_description")}( - {patientList[0].phone_number}) + {patientList[0]?.phone_number})

diff --git a/src/components/Form/FormFields/PhoneNumberFormField.tsx b/src/components/Form/FormFields/PhoneNumberFormField.tsx index 72a3e0c3076..905d6e92226 100644 --- a/src/components/Form/FormFields/PhoneNumberFormField.tsx +++ b/src/components/Form/FormFields/PhoneNumberFormField.tsx @@ -104,7 +104,13 @@ const PhoneNumberFormField = React.forwardRef( setCountry({ flag: "🌍", name: "Other", code: "+" }); return; } - setCountry(phoneCodes[getCountryCode(field.value)!]); + const countryCode = getCountryCode(field.value); + if (countryCode) { + const countryData = phoneCodes[countryCode]; + if (countryData) { + setCountry(countryData); + } + } } }, [field.value]); @@ -186,7 +192,7 @@ const PhoneNumberTypesHelp = (props: { types: PhoneNumberType[] }) => { ); }; const conditionPhoneCode = (code: string) => { - code = code.split(" ")[0]; + code = code.split(" ")[0] ?? ""; return code.startsWith("+") ? code : "+" + code; }; diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 2cfade10b39..474a5c26ca5 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -471,11 +471,15 @@ export default function PatientRegistration( } > - {[ - ["dob", t("date_of_birth")], - ["age", t("age")], - ].map(([key, label]) => ( - {label} + {( + [ + ["dob", t("date_of_birth")], + ["age", t("age")], + ] as const + ).map(([key, label]) => ( + + {label} + ))} diff --git a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx index 916f97018e5..f525c3ec997 100644 --- a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx @@ -50,7 +50,7 @@ export function AppointmentQuestion({ (questionnaireResponse.values?.[0] ?.value as unknown as CreateAppointmentQuestion[]) || []; - const value = values[0] ?? {}; + const value: Partial = values[0] ?? {}; const handleUpdate = (updates: Partial) => { const appointment = { ...value, ...updates }; diff --git a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx index b20629e171c..2a1a9c15ba4 100644 --- a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx @@ -55,8 +55,13 @@ export function DateTimeQuestion({ }; const handleTimeChange = (event: React.ChangeEvent) => { - const [hours, minutes] = event.target.value.split(":").map(Number); - if (isNaN(hours) || isNaN(minutes)) return; + const parts = event.target.value.split(":"); + if (parts.length !== 2) return; + const [hoursStr, minutesStr] = parts; + const hours = Number(hoursStr); + const minutes = Number(minutesStr); + + if (Number.isNaN(hours) || Number.isNaN(minutes)) return; const date = currentValue || new Date(); date.setHours(hours); diff --git a/src/components/Questionnaire/QuestionnaireEditor.tsx b/src/components/Questionnaire/QuestionnaireEditor.tsx index c6101d99f1b..5e4cc906308 100644 --- a/src/components/Questionnaire/QuestionnaireEditor.tsx +++ b/src/components/Questionnaire/QuestionnaireEditor.tsx @@ -131,7 +131,9 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { const items = Array.from(questionnaire.questions); const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); + if (reorderedItem) { + items.splice(result.destination.index, 0, reorderedItem); + } updateQuestionnaireField("questions", items); }; @@ -1147,7 +1149,9 @@ function QuestionEditor({ const items = Array.from(questions || []); const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); + if (reorderedItem) { + items.splice(result.destination.index, 0, reorderedItem); + } updateField("questions", items); }} diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index 34e67e9eb02..4d2328a6479 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -151,18 +151,20 @@ export function QuestionnaireForm({ const updatedForms = [...questionnaireForms]; const errorMessages: string[] = []; - results.forEach((result, index) => { + results?.forEach((result, index) => { const form = updatedForms[index]; result.data.errors.forEach( (error: QuestionValidationError | DetailedValidationError) => { // Handle question-specific errors if ("question_id" in error) { - form.errors.push({ + form?.errors.push({ question_id: error.question_id, error: error.error ?? error.msg, } as QuestionValidationError); - updatedForms[index] = form; + if (form) { + updatedForms[index] = form; + } } // Handle form-level errors diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 8e5fdba0078..5e281dc641d 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -149,7 +149,7 @@ const handlers: { }, appointment: { getRequests: (appointment, { facilityId, patientId }) => { - const { reason_for_visit, slot_id } = appointment[0]; + const { reason_for_visit = "", slot_id } = appointment[0] ?? {}; return [ { url: `/api/v1/facility/${facilityId}/slots/${slot_id}/create_appointment/`, diff --git a/src/components/Users/UserAvailabilityTab.tsx b/src/components/Users/UserAvailabilityTab.tsx index 3fed5518c6c..0f657edc97e 100644 --- a/src/components/Users/UserAvailabilityTab.tsx +++ b/src/components/Users/UserAvailabilityTab.tsx @@ -197,13 +197,14 @@ export default function UserAvailabilityTab({ userData: user }: Props) {

{slot_type === "appointment" && (

- {Math.floor( - getSlotsPerSession( - availability[0].start_time, - availability[0].end_time, - slot_size_in_minutes, - ) ?? 0, - )}{" "} + {availability[0] && + Math.floor( + getSlotsPerSession( + availability[0].start_time, + availability[0].end_time, + slot_size_in_minutes, + ) ?? 0, + )}{" "} slots of {slot_size_in_minutes} mins.

)} @@ -303,7 +304,9 @@ const diagonalStripes = { export const formatAvailabilityTime = ( availability: AvailabilityDateTime[], ) => { - const startTime = availability[0].start_time; - const endTime = availability[0].end_time; - return `${formatTimeShort(startTime)} - ${formatTimeShort(endTime)}`; + if (availability[0]) { + const startTime = availability[0].start_time; + const endTime = availability[0].end_time; + return `${formatTimeShort(startTime)} - ${formatTimeShort(endTime)}`; + } }; diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx index 5887b4d3acb..07289a66ab4 100644 --- a/src/components/ui/chart.tsx +++ b/src/components/ui/chart.tsx @@ -137,7 +137,7 @@ const ChartTooltipContent = React.forwardRef< } const [item] = payload; - const key = `${labelKey || item.dataKey || item.name || "value"}`; + const key = `${labelKey || item?.dataKey || item?.name || "value"}`; const itemConfig = getPayloadConfigFromPayload(config, item, key); const value = !labelKey && typeof label === "string" diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx index ea07b294c56..9d150705448 100644 --- a/src/components/ui/input-otp.tsx +++ b/src/components/ui/input-otp.tsx @@ -33,7 +33,7 @@ const InputOTPSlot = React.forwardRef< React.ComponentPropsWithoutRef<"div"> & { index: number } >(({ index, className, ...props }, ref) => { const inputOTPContext = React.useContext(OTPInputContext); - const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] ?? {}; return (
{ - if (history.length > 1) + if (history.length > 1) { // Otherwise, navigate to history present in the app navigation history stack. - return navigate(history[1]); + if (history[1]) return navigate(history[1]); + } if (fallbackUrl) // use provided fallback url if provided. diff --git a/src/hooks/useFileManager.tsx b/src/hooks/useFileManager.tsx index e6c92ff5166..82fafce3b88 100644 --- a/src/hooks/useFileManager.tsx +++ b/src/hooks/useFileManager.tsx @@ -81,8 +81,9 @@ export default function useFileManager( const queryClient = useQueryClient(); const getExtension = (url: string) => { - const div1 = url.split("?")[0].split("."); - const ext: string = div1[div1.length - 1].toLowerCase(); + const div1 = url.split("?")[0]?.split(".") || []; + const ext: string = + div1.length > 0 ? (div1[div1.length - 1]?.toLowerCase() ?? "") : ""; return ext; }; diff --git a/src/hooks/useFileUpload.tsx b/src/hooks/useFileUpload.tsx index d5884dddd02..dc65a310c61 100644 --- a/src/hooks/useFileUpload.tsx +++ b/src/hooks/useFileUpload.tsx @@ -105,7 +105,7 @@ export default function useFileUpload( setFiles((prev) => [...prev, ...selectedFiles]); selectedFiles.forEach((file) => { - const ext: string = file.name.split(".")[1]; + const ext: string = file.name.split(".")[1] ?? ""; if (ExtImage.includes(ext)) { const options = { initialQuality: 0.6, diff --git a/src/hooks/useFilters.tsx b/src/hooks/useFilters.tsx index c5984a70c9c..66a48e9bbc1 100644 --- a/src/hooks/useFilters.tsx +++ b/src/hooks/useFilters.tsx @@ -94,7 +94,9 @@ export default function useFilters({ name={name} value={ value === undefined - ? humanizeStrings(paramKey.map((k) => qParams[k]).filter(Boolean)) + ? (humanizeStrings( + paramKey.map((k) => qParams[k]).filter(Boolean), + ) ?? "") : value } onRemove={() => removeFilters(paramKey)} @@ -128,7 +130,10 @@ export default function useFilters({ }, range(name: string, paramKey: string, minKey = "min", maxKey = "max") { const paramKeys = [paramKey + "_" + minKey, paramKey + "_" + maxKey]; - const values = [qParams[paramKeys[0]], qParams[paramKeys[1]]]; + const values = [ + paramKeys[0] && qParams[paramKeys[0]], + paramKeys[1] && qParams[paramKeys[1]], + ]; if (values[0] === values[1]) return [{ name, value: values[0], paramKey: paramKeys }]; return [name + " " + minKey, name + " " + maxKey].map((name, i) => { diff --git a/src/pages/Appointments/utils.ts b/src/pages/Appointments/utils.ts index 4b131e50382..b01e0bfb798 100644 --- a/src/pages/Appointments/utils.ts +++ b/src/pages/Appointments/utils.ts @@ -51,9 +51,12 @@ export const groupSlotsByAvailability = (slots: TokenSlot[]) => { ); // sort availability by first slot start time - result.sort((a, b) => - compareAsc(a.slots[0].start_datetime, b.slots[0].start_datetime), - ); + result.sort((a, b) => { + if (a.slots[0]?.start_datetime && b.slots[0]?.start_datetime) { + return compareAsc(a.slots[0].start_datetime, b.slots[0].start_datetime); + } + return 0; + }); return result; }; diff --git a/src/pages/Encounters/EncounterShow.tsx b/src/pages/Encounters/EncounterShow.tsx index 0440aa49aae..b68383ed927 100644 --- a/src/pages/Encounters/EncounterShow.tsx +++ b/src/pages/Encounters/EncounterShow.tsx @@ -166,8 +166,7 @@ export const EncounterShow = (props: Props) => { if (!encounterData) { return ; } - - const SelectedTab = tabs[props.tab]; + const SelectedTab = tabs[props.tab] || EncounterUpdatesTab; const tabButtonClasses = (selected: boolean) => `capitalize min-w-max-content cursor-pointer font-bold whitespace-nowrap ${ diff --git a/src/pages/Encounters/PrintPrescription.tsx b/src/pages/Encounters/PrintPrescription.tsx index b5f8f8ec4e6..3e7225a6dfb 100644 --- a/src/pages/Encounters/PrintPrescription.tsx +++ b/src/pages/Encounters/PrintPrescription.tsx @@ -194,10 +194,10 @@ export const PrintPrescription = (props: { {Array.from(usedFrequencies).map((key) => (
- {FREQUENCY_DISPLAY[key].code} + {FREQUENCY_DISPLAY[key]?.code} - = {FREQUENCY_DISPLAY[key].meaning} + = {FREQUENCY_DISPLAY[key]?.meaning}
))} diff --git a/src/pages/Encounters/tabs/EncounterNotesTab.tsx b/src/pages/Encounters/tabs/EncounterNotesTab.tsx index d8a93b592c5..8cb2b176d3c 100644 --- a/src/pages/Encounters/tabs/EncounterNotesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterNotesTab.tsx @@ -330,7 +330,11 @@ export const EncounterNotesTab = ({ encounter }: EncounterTabProps) => { // Auto-select first thread useEffect(() => { - if (threadsData?.results.length && !selectedThread) { + if ( + threadsData?.results.length && + threadsData.results[0] && + !selectedThread + ) { setSelectedThread(threadsData.results[0].id); } }, [threadsData, selectedThread]); diff --git a/src/pages/Encounters/tabs/EncounterPlotsTab.tsx b/src/pages/Encounters/tabs/EncounterPlotsTab.tsx index cc9c6d57689..b039dfc342c 100644 --- a/src/pages/Encounters/tabs/EncounterPlotsTab.tsx +++ b/src/pages/Encounters/tabs/EncounterPlotsTab.tsx @@ -30,7 +30,7 @@ export const EncounterPlotsTab = (props: EncounterTabProps) => { return ; } - const currentTabId = qParams.plot || data[0].id; + const currentTabId = qParams.plot || data[0]?.id; const currentTab = data.find((tab) => tab.id === currentTabId); if (!currentTab) { diff --git a/src/pages/FacilityOrganization/components/FacilityOrganizationSelector.tsx b/src/pages/FacilityOrganization/components/FacilityOrganizationSelector.tsx index b9c32974a82..5267da41a1c 100644 --- a/src/pages/FacilityOrganization/components/FacilityOrganizationSelector.tsx +++ b/src/pages/FacilityOrganization/components/FacilityOrganizationSelector.tsx @@ -100,8 +100,10 @@ export default function FacilityOrganizationSelector( setSelectedLevels(newLevels); if (newLevels.length > 0) { const lastOrg = newLevels[newLevels.length - 1]; - setSelectedOrganization(lastOrg); - onChange(lastOrg.id); + if (lastOrg) { + setSelectedOrganization(lastOrg); + onChange(lastOrg.id); + } } else { setSelectedOrganization(null); } diff --git a/src/pages/Scheduling/ScheduleTemplates.tsx b/src/pages/Scheduling/ScheduleTemplates.tsx index 5d32b332910..7877efbf3a0 100644 --- a/src/pages/Scheduling/ScheduleTemplates.tsx +++ b/src/pages/Scheduling/ScheduleTemplates.tsx @@ -125,13 +125,14 @@ const ScheduleTemplateItem = ({ <> | - {Math.floor( - getSlotsPerSession( - slot.availability[0].start_time, - slot.availability[0].end_time, - slot.slot_size_in_minutes, - ) ?? 0, - )}{" "} + {slot.availability[0] && + Math.floor( + getSlotsPerSession( + slot.availability[0].start_time, + slot.availability[0].end_time, + slot.slot_size_in_minutes, + ) ?? 0, + )}{" "} slots of {slot.slot_size_in_minutes} mins. diff --git a/src/pages/Scheduling/components/EditScheduleTemplateSheet.tsx b/src/pages/Scheduling/components/EditScheduleTemplateSheet.tsx index cd8b01d7020..0e55eef5bbc 100644 --- a/src/pages/Scheduling/components/EditScheduleTemplateSheet.tsx +++ b/src/pages/Scheduling/components/EditScheduleTemplateSheet.tsx @@ -330,13 +330,15 @@ const AvailabilityEditor = ({ if (availability.slot_type !== "appointment") return { totalSlots: null, tokenDuration: null }; - const slots = Math.floor( - getSlotsPerSession( - availability.availability[0].start_time, - availability.availability[0].end_time, - availability.slot_size_in_minutes, - ) ?? 0, - ); + const slots = + availability.availability[0] && + Math.floor( + getSlotsPerSession( + availability.availability[0].start_time, + availability.availability[0].end_time, + availability.slot_size_in_minutes, + ) ?? 0, + ); const duration = getTokenDuration( availability.slot_size_in_minutes, @@ -432,19 +434,24 @@ const AvailabilityEditor = ({ {t("schedule")}
- {Object.entries(availabilitiesByDay).map(([day, times]) => ( -

- - {DayOfWeek[parseInt(day)].charAt(0) + - DayOfWeek[parseInt(day)].slice(1).toLowerCase()} - - - {times - .map((time) => formatAvailabilityTime([time])) - .join(", ")} - -

- ))} + {Object.entries(availabilitiesByDay).map(([day, times]) => { + const dayofWeek = DayOfWeek[parseInt(day)]; + if (!dayofWeek) return; + const formattedDay = + dayofWeek?.charAt(0) + dayofWeek?.slice(1).toLowerCase(); + return ( +

+ + {formattedDay} + + + {times + .map((time) => formatAvailabilityTime([time])) + .join(", ")} + +

+ ); + })}
diff --git a/src/service-worker.ts b/src/service-worker.ts index 078e4237c28..d2f832f91de 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -35,7 +35,7 @@ self.addEventListener("push", async function (event) { if (["PUSH_MESSAGE", "MESSAGE"].includes(data?.type)) { self.clients.matchAll().then((clients) => { - clients[0].postMessage(data); + clients[0]?.postMessage(data); }); } else { event.waitUntil( diff --git a/tsconfig.json b/tsconfig.json index bba22c5da97..11113ca92df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ "@core/*": ["src/*"], "@careConfig": ["./care.config.ts"] }, - "typeRoots": ["./node_modules/@types", "./src/remote-modules.d.ts"] + "typeRoots": ["./node_modules/@types", "./src/remote-modules.d.ts"], + "noUncheckedIndexedAccess": true }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "care.config.ts"], "exclude": ["src/**/*.gen.tsx"]