From dc8dec018d633be9f9c61837a038d7d215b36818 Mon Sep 17 00:00:00 2001 From: konavivekramakrishna <101407963+konavivekramakrishna@users.noreply.github.com> Date: Wed, 6 Dec 2023 09:21:22 +0530 Subject: [PATCH 1/5] Modified the external results sample format (#6694) * Update sample_format_external_result_import URL * Fix file path in config.json and refactor handleDownload function in ExternalResultUpload.tsx * Fix download functionality in ExternalResultUpload component --- public/External-Results-Template.csv | 3 +++ public/config.json | 2 +- src/Components/ExternalResult/ExternalResultUpload.tsx | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 public/External-Results-Template.csv diff --git a/public/External-Results-Template.csv b/public/External-Results-Template.csv new file mode 100644 index 00000000000..3ab2afc65ba --- /dev/null +++ b/public/External-Results-Template.csv @@ -0,0 +1,3 @@ +District,srf id,name,age,age in,gender,mobile number,address,ward,local body,local body type,source,Sample Collection Date,result date,test type,lab name,sample type,patient status,Is Repeat,patient category,result +Ernakulam,00/EKM/0000,Bodhi CSN,24,years,m,8888888888,"CSN HQ +Kochi, Kerala ",7,Poothrikka,grama panchayath,Secondary contact aparna,2020-10-14,2020-10-14,Antigen,Karothukuzhi Laboratory,Ag-SD_Biosensor_Standard_Q_COVID-19_Ag_detection_kit,Asymptomatic,NO,Cat 17: All individuals who wish to get themselves tested,Negative \ No newline at end of file diff --git a/public/config.json b/public/config.json index 444362cfb20..74e509fee59 100644 --- a/public/config.json +++ b/public/config.json @@ -21,6 +21,6 @@ "kasp_string": "KASP", "kasp_full_string": "Karunya Arogya Suraksha Padhathi", "sample_format_asset_import": "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=11JaEhNHdyCHth4YQs_44YaRlP77Rrqe81VSEfg1glko&exportFormat=xlsx", - "sample_format_external_result_import": "https://docs.google.com/spreadsheets/d/17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE/export?format=csv&id=17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE", + "sample_format_external_result_import": "/External-Results-Template.csv", "enable_abdm": true } \ No newline at end of file diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 20a2cec3341..5a9262b2990 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -112,6 +112,8 @@ export default function ExternalResultUpload() { {" "} {t("sample_format")} From 5e139447f924dad9747e29c00732334932ca8c34 Mon Sep 17 00:00:00 2001 From: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:24:36 +0530 Subject: [PATCH 2/5] seperated bedname and location name (#6794) --- src/Components/Common/BedSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Common/BedSelect.tsx b/src/Components/Common/BedSelect.tsx index d903d3b62e0..94caded12a3 100644 --- a/src/Components/Common/BedSelect.tsx +++ b/src/Components/Common/BedSelect.tsx @@ -74,7 +74,7 @@ export const BedSelect = (props: BedSelectProps) => { optionLabel={(option: any) => { if (Object.keys(option).length === 0) return ""; return ( - `${option.name} ${option?.location_object?.name || t("unknown")}` || + `${option.name}, ${option?.location_object?.name || t("unknown")}` || option?.location_object?.name ); }} From f746576b04a63ab7acd018cf2e2ea181deb21aea Mon Sep 17 00:00:00 2001 From: konavivekramakrishna <101407963+konavivekramakrishna@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:25:44 +0530 Subject: [PATCH 3/5] Added a Date/time and round type filter for log updates (#6713) * Add DailyRoundsFilterModel interface and DailyRoundsFilter component * refactored dailyRoundList * useQuery: Fix GET/HEAD cannot contain body issue * Support for filtering by `taken_at` dt range * Refactor DailyRoundsFilter and DailyRoundsList components * Refactor roundTypeOptions in DailyRoundsFilter component * Improve translations coverage and minor refactors * update slugs for request --------- Co-authored-by: rithviknishad --- .../ConsultationUpdatesTab.tsx | 29 +--- .../DailyRounds/DefaultLogUpdateCard.tsx | 3 +- .../Consultations/DailyRoundsFilter.tsx | 115 +++++++++++++++ .../Consultations/DailyRoundsList.tsx | 134 +++++++++--------- src/Components/Facility/models.tsx | 2 +- .../Form/FormFields/SelectFormField.tsx | 2 +- src/Components/Patient/models.tsx | 7 +- src/Locale/en/Common.json | 3 +- src/Locale/en/Consultation.json | 5 +- src/Utils/request/utils.ts | 5 +- 10 files changed, 201 insertions(+), 104 deletions(-) create mode 100644 src/Components/Facility/Consultations/DailyRoundsFilter.tsx diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index bb584ae93e6..0d8a70781da 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -12,13 +12,12 @@ import PrescriptionsTable from "../../Medicine/PrescriptionsTable"; import Chip from "../../../CAREUI/display/Chip"; import { formatAge, formatDate, formatDateTime } from "../../../Utils/utils"; import ReadMore from "../../Common/components/Readmore"; -import { DailyRoundsList } from "../Consultations/DailyRoundsList"; +import DailyRoundsList from "../Consultations/DailyRoundsList"; const PageTitle = lazy(() => import("../../Common/PageTitle")); export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { const dispatch: any = useDispatch(); - const [showAutomatedRounds, setShowAutomatedRounds] = useState(true); const [hl7SocketUrl, setHL7SocketUrl] = useState(); const [ventilatorSocketUrl, setVentilatorSocketUrl] = useState(); const [monitorBedData, setMonitorBedData] = useState(); @@ -674,31 +673,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
-
- -
- setShowAutomatedRounds((s) => !s)} - /> - -
-
- +
diff --git a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx index 63b5087cff8..ff738f4acb6 100644 --- a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx @@ -4,10 +4,11 @@ import CareIcon from "../../../../CAREUI/icons/CareIcon"; import ButtonV2 from "../../../Common/components/ButtonV2"; import { DailyRoundsModel } from "../../../Patient/models"; import LogUpdateCardAttribute from "./LogUpdateCardAttribute"; +import { ConsultationModel } from "../../models"; interface Props { round: DailyRoundsModel; - consultationData: any; + consultationData: ConsultationModel; onViewDetails: () => void; onUpdateLog?: () => void; } diff --git a/src/Components/Facility/Consultations/DailyRoundsFilter.tsx b/src/Components/Facility/Consultations/DailyRoundsFilter.tsx new file mode 100644 index 00000000000..62b8d63e824 --- /dev/null +++ b/src/Components/Facility/Consultations/DailyRoundsFilter.tsx @@ -0,0 +1,115 @@ +import { Popover, Transition } from "@headlessui/react"; +import ButtonV2 from "../../Common/components/ButtonV2"; +import { Fragment } from "react"; +import { SelectFormField } from "../../Form/FormFields/SelectFormField"; +import TextFormField from "../../Form/FormFields/TextFormField"; +import CareIcon from "../../../CAREUI/icons/CareIcon"; +import dayjs from "dayjs"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DailyRoundTypes, DailyRoundsModel } from "../../Patient/models"; +import { FieldChangeEvent } from "../../Form/FormFields/Utils"; + +type FilterState = { + rounds_type?: DailyRoundsModel["rounds_type"]; + taken_at_after?: string; + taken_at_before?: string; +}; + +interface Props { + onApply: (filter: FilterState) => void; +} + +export default function DailyRoundsFilter(props: Props) { + const { t } = useTranslation(); + const [filter, setFilter] = useState({}); + + const field = (name: keyof FilterState) => ({ + name, + value: filter[name], + onChange: (e: FieldChangeEvent) => + setFilter({ ...filter, [e.name]: e.value }), + labelClassName: "text-sm", + errorClassName: "hidden", + }); + + return ( +
+ + + + + {t("filter")} + + + + +
+
+
+ + {t("filter_by")} + +
+
+
+ t(o)} + optionValue={(o) => o} + /> + + + + + { + setFilter({}); + props.onApply({}); + }} + border + className="w-full" + > + {t("clear")} + + + + props.onApply(filter)} + border + className="w-full" + > + {t("apply")} + + +
+
+
+
+
+
+ ); +} diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx index ffc70ddf175..2060d8657e7 100644 --- a/src/Components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx @@ -6,88 +6,84 @@ import { useTranslation } from "react-i18next"; import LoadingLogUpdateCard from "./DailyRounds/LoadingCard"; import routes from "../../../Redux/api"; import PaginatedList from "../../../CAREUI/misc/PaginatedList"; +import PageTitle from "../../Common/PageTitle"; +import DailyRoundsFilter from "./DailyRoundsFilter"; +import { ConsultationModel } from "../models"; +import { useSlugs } from "../../../Common/hooks/useSlug"; -export const DailyRoundsList = (props: any) => { +interface Props { + consultation: ConsultationModel; +} + +export default function DailyRoundsList({ consultation }: Props) { + const [facilityId, patientId, consultationId] = useSlugs( + "facility", + "patient", + "consultation" + ); const { t } = useTranslation(); - const { - facilityId, - patientId, - consultationId, - consultationData, - showAutomatedRounds, - } = props; + + const consultationUrl = `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`; return ( - {(_) => ( -
-
- - - {t("no_consultation_updates")} - - - - <> - {Array.from({ length: 3 }).map((_, i) => ( - - ))} - - - className="flex grow flex-col gap-3"> - {(item, items) => { - if (item.rounds_type === "AUTOMATED") { + {({ refetch }) => ( + <> +
+ + refetch({ query })} /> +
+ +
+
+ + + {t("no_consultation_updates")} + + + + <> + {Array.from({ length: 3 }).map((_, i) => ( + + ))} + + + className="flex grow flex-col gap-3"> + {(item, items) => { + if (item.rounds_type === "AUTOMATED") { + return ( + + ); + } + + const itemUrl = + item.rounds_type === "NORMAL" + ? `${consultationUrl}/daily-rounds/${item.id}` + : `${consultationUrl}/daily_rounds/${item.id}`; + return ( - navigate(itemUrl)} + onUpdateLog={() => navigate(`${itemUrl}/update`)} /> ); - } - return ( - { - if (item.rounds_type === "NORMAL") { - navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily-rounds/${item.id}` - ); - } else { - navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily_rounds/${item.id}` - ); - } - }} - onUpdateLog={() => { - if (item.rounds_type === "NORMAL") { - navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily-rounds/${item.id}/update` - ); - } else { - navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily_rounds/${item.id}/update` - ); - } - }} - /> - ); - }} - -
- + }} + +
+ +
-
+ )} ); -}; +} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index cdc7074c145..550012603f5 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -107,7 +107,7 @@ export interface ConsultationModel { history_of_present_illness?: string; facility?: number; facility_name?: string; - id?: string; + id: string; modified_date?: string; other_symptoms?: string; patient?: string; diff --git a/src/Components/Form/FormFields/SelectFormField.tsx b/src/Components/Form/FormFields/SelectFormField.tsx index 3c6613bb662..5afe4f11d63 100644 --- a/src/Components/Form/FormFields/SelectFormField.tsx +++ b/src/Components/Form/FormFields/SelectFormField.tsx @@ -7,7 +7,7 @@ type OptionCallback = (option: T) => R; type SelectFormFieldProps = FormFieldBaseProps & { placeholder?: React.ReactNode; - options: T[]; + options: readonly T[]; position?: "above" | "below"; optionLabel: OptionCallback; optionSelectedLabel?: OptionCallback; diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 341e13e3c80..af69d8464bc 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -269,6 +269,8 @@ export interface DailyRoundsOutput { quantity: number; } +export const DailyRoundTypes = ["NORMAL", "VENTILATOR", "AUTOMATED"] as const; + export interface DailyRoundsModel { ventilator_spo2?: number; spo2?: string; @@ -290,7 +292,7 @@ export interface DailyRoundsModel { medication_given?: Array; additional_symptoms_text?: string; current_health?: string; - id?: any; + id: string; other_symptoms?: string; admitted_to?: string; patient_category?: PatientCategory; @@ -299,7 +301,7 @@ export interface DailyRoundsModel { created_date?: string; modified_date?: string; taken_at?: string; - rounds_type?: "NORMAL" | "VENTILATOR" | "ICU" | "AUTOMATED"; + rounds_type: (typeof DailyRoundTypes)[number]; last_updated_by_telemedicine?: boolean; created_by_telemedicine?: boolean; created_by?: { @@ -314,6 +316,7 @@ export interface DailyRoundsModel { }; bed?: string; } + export interface FacilityNameModel { id?: string; name?: string; diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index 7e357bc04b5..c455e3a989a 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -45,6 +45,7 @@ "clear": "Clear", "apply": "Apply", "filter_by": "Filter By", + "filter": "Filter", "ordering": "Ordering", "phone_number": "Phone Number", "emergency_contact_number": "Emergency Contact Number", @@ -158,4 +159,4 @@ "clear_selection": "Clear selection", "select_date": "Select date", "DD/MM/YYYY": "DD/MM/YYYY" -} +} \ No newline at end of file diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json index a40f03c4f24..54b587eb81e 100644 --- a/src/Locale/en/Consultation.json +++ b/src/Locale/en/Consultation.json @@ -12,5 +12,8 @@ "discharge_summary_not_ready": "Discharge summary is not ready yet.", "download_discharge_summary": "Download discharge summary", "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary", - "generated_summary_caution": "This is a computer generated summary using the information captured in the CARE system." + "generated_summary_caution": "This is a computer generated summary using the information captured in the CARE system.", + "NORMAL": "Normal", + "VENTILATOR": "Critical Care", + "AUTOMATED": "Automated" } diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index ec919c79490..f22dca369f2 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -82,7 +82,10 @@ export function mergeRequestOptions( ...overrides, query: { ...options.query, ...overrides.query }, - body: { ...(options.body ?? {}), ...(overrides.body ?? {}) }, + body: (options.body || overrides.body) && { + ...(options.body ?? {}), + ...(overrides.body ?? {}), + }, pathParams: { ...options.pathParams, ...overrides.pathParams }, onResponse: (res) => { From 947978c3f0215eefaac1a2dd2a85dbb7bae5bc37 Mon Sep 17 00:00:00 2001 From: Ashraf Mohammed <98876115+AshrafMd-1@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:26:36 +0530 Subject: [PATCH 4/5] Use relative time for audit log details. (#6640) * convert time to relative time * change styling * change margin * add new classname --- src/CAREUI/display/RecordMeta.tsx | 14 +++++++++--- src/Components/Shifting/ShiftDetails.tsx | 27 +++++++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/CAREUI/display/RecordMeta.tsx b/src/CAREUI/display/RecordMeta.tsx index 944ddf27c8f..818553d9207 100644 --- a/src/CAREUI/display/RecordMeta.tsx +++ b/src/CAREUI/display/RecordMeta.tsx @@ -11,6 +11,7 @@ interface Props { time?: string; prefix?: ReactNode; className?: string; + inlineClassName?: string; user?: { first_name: string; last_name: string; @@ -23,7 +24,14 @@ interface Props { * A generic component to display relative time along with a tooltip and a user * if provided. */ -const RecordMeta = ({ time, user, prefix, className, inlineUser }: Props) => { +const RecordMeta = ({ + time, + user, + prefix, + className, + inlineClassName, + inlineUser, +}: Props) => { const isOnline = user && isUserOnline(user); let child = ( @@ -47,11 +55,11 @@ const RecordMeta = ({ time, user, prefix, className, inlineUser }: Props) => { if (prefix || user) { child = ( -
+
{prefix} {child} {user && inlineUser && by} - {user && } + {user && !inlineUser && } {user && inlineUser && ( {formatName(user)} )} diff --git a/src/Components/Shifting/ShiftDetails.tsx b/src/Components/Shifting/ShiftDetails.tsx index 3d2a1f60b8b..a898b0cde15 100644 --- a/src/Components/Shifting/ShiftDetails.tsx +++ b/src/Components/Shifting/ShiftDetails.tsx @@ -806,13 +806,12 @@ export default function ShiftDetails(props: { id: string }) { {t("created")}
-
- {data?.created_by_object?.first_name} - {data?.created_by_object?.last_name} -
-
- {data?.created_date && formatDateTime(data?.created_date)} -
+
@@ -820,14 +819,12 @@ export default function ShiftDetails(props: { id: string }) { {t("last_edited")}
-
- {data?.last_edited_by_object?.first_name}{" "} - {data?.last_edited_by_object?.last_name} -
-
- {data?.modified_date && - formatDateTime(data?.modified_date)} -
+
From a6ed2bc82f0297ab430f961c6f4384ccad37dd65 Mon Sep 17 00:00:00 2001 From: Devdeep Ghosh <63492939+thedevildude@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:27:17 +0530 Subject: [PATCH 5/5] Replaced useDispatch w. useQuery/request: Users (src/Components/Users/**) [5 out of 5 components] (#6596) * replaced getUserDetails and getUserListSkills actions with useQuery * replaced partialUpdateUser action with request * replaced updatePassword action with request * replaced dispatch with useQuery in UserFilter.tsx * Bug Fix: UserFilter tried fetching district when district_id was not available * addUser and checkUsername action replaced with request * replaced useDispatch with request in UserAdd component * replaced useDispatch with useQuery and request in SkillsSlideOver * replaced useDispatch with useQuery and request in ManageUsers * solved issue #6652 | passed user skills as props to SkillSelect * removed unnecessary console logs * re-added showAll as dependency to skillSearch * removed unnecessary console logs * replaced fetchDistrict request with useQuery and removed isLoading * fixed error notification in ManageUsers * removed unnecessary useState from ManageUsers * code fixes in SkillsSlideOver * code fixes in UserAdd * code fix in UserFilter * code fix in UserProfile * removed redundant code * added proper types and fixed redundant code * removed redundant fireRequest actions * fix http 301 redirect due to missing trailing slash * Update TRes of userListFacility in src/Redux/api.tsx Co-authored-by: Rithvik Nishad * resolved imports --------- Co-authored-by: Rithvik Nishad Co-authored-by: Rithvik Nishad Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/Components/Common/SkillSelect.tsx | 21 +- src/Components/Facility/models.tsx | 1 + src/Components/Users/ManageUsers.tsx | 219 +++++++++------------ src/Components/Users/SkillsSlideOver.tsx | 67 +++---- src/Components/Users/UserAdd.tsx | 164 +++++----------- src/Components/Users/UserFilter.tsx | 25 +-- src/Components/Users/UserProfile.tsx | 237 +++++++++++------------ src/Components/Users/models.tsx | 14 +- src/Redux/actions.tsx | 35 +--- src/Redux/api.tsx | 36 +++- 10 files changed, 344 insertions(+), 475 deletions(-) diff --git a/src/Components/Common/SkillSelect.tsx b/src/Components/Common/SkillSelect.tsx index 3257971d03c..941c29790d1 100644 --- a/src/Components/Common/SkillSelect.tsx +++ b/src/Components/Common/SkillSelect.tsx @@ -1,8 +1,8 @@ import { useCallback } from "react"; import { useDispatch } from "react-redux"; -import { getAllSkills, getUserListSkills } from "../../Redux/actions"; +import { getAllSkills } from "../../Redux/actions"; import AutoCompleteAsync from "../Form/AutoCompleteAsync"; -import { SkillObjectModel } from "../Users/models"; +import { SkillModel, SkillObjectModel } from "../Users/models"; interface SkillSelectProps { id?: string; @@ -17,6 +17,7 @@ interface SkillSelectProps { selected: SkillObjectModel | SkillObjectModel[] | null; setSelected: (selected: SkillObjectModel) => void; username?: string; + userSkills?: SkillModel[]; } export const SkillSelect = (props: SkillSelectProps) => { @@ -32,7 +33,8 @@ export const SkillSelect = (props: SkillSelectProps) => { disabled = false, className = "", errors = "", - username, + //username, + userSkills, } = props; const dispatchAction: any = useDispatch(); @@ -47,21 +49,16 @@ export const SkillSelect = (props: SkillSelectProps) => { }; const res = await dispatchAction(getAllSkills(params)); - - const linkedSkills = await dispatchAction( - getUserListSkills({ username: username }) - ); - - const skillsList = linkedSkills?.data?.results; const skillsID: string[] = []; - skillsList.map((skill: any) => skillsID.push(skill.skill_object.id)); + userSkills?.map((skill: SkillModel) => + skillsID.push(skill.skill_object.id) + ); const skills = res?.data?.results.filter( (skill: any) => !skillsID.includes(skill.id) ); - return skills; }, - [dispatchAction, searchAll, showAll] + [dispatchAction, searchAll, userSkills, showAll] ); return ( diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 550012603f5..0d8a112e021 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -7,6 +7,7 @@ import { RouteToFacility } from "../Common/RouteToFacilitySelect"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; export interface LocalBodyModel { + id: number; name: string; body_type: number; localbody_code: string; diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx index 94791e55ae1..3b469081754 100644 --- a/src/Components/Users/ManageUsers.tsx +++ b/src/Components/Users/ManageUsers.tsx @@ -1,17 +1,5 @@ import * as Notification from "../../Utils/Notifications.js"; -import { - addUserFacility, - clearHomeFacility, - deleteUser, - deleteUserFacility, - getDistrict, - getUserList, - getUserListFacility, - partialUpdateUser, -} from "../../Redux/actions"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { lazy, useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useState } from "react"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import ButtonV2, { Submit } from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -36,6 +24,9 @@ import Page from "../Common/components/Page.js"; import dayjs from "dayjs"; import TextFormField from "../Form/FormFields/TextFormField.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; +import routes from "../../Redux/api.js"; +import useQuery from "../../Utils/request/useQuery.js"; +import request from "../../Utils/request/request.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -49,19 +40,13 @@ export default function ManageUsers() { advancedFilter, resultsPerPage, } = useFilters({ limit: 18 }); - const dispatch: any = useDispatch(); - const initialData: any[] = []; let manageUsers: any = null; - const [users, setUsers] = useState(initialData); - const [isLoading, setIsLoading] = useState(false); const [expandSkillList, setExpandSkillList] = useState(false); - const [totalCount, setTotalCount] = useState(0); - const [districtName, setDistrictName] = useState(); const [expandFacilityList, setExpandFacilityList] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [expandWorkingHours, setExpandWorkingHours] = useState(false); const authUser = useAuthUser(); - const [weeklyHours, setWeeklyHours] = useState(0); + const [weeklyHours, setWeeklyHours] = useState("0"); const userIndex = USER_TYPES.indexOf(authUser.user_type); const userTypes = authUser.is_superuser ? [...USER_TYPES] @@ -79,58 +64,32 @@ export default function ManageUsers() { const isExtremeSmallScreen = width <= extremeSmallScreenBreakpoint ? true : false; - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const params = { - limit: resultsPerPage, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - username: qParams.username, - first_name: qParams.first_name, - last_name: qParams.last_name, - phone_number: qParams.phone_number, - alt_phone_number: qParams.alt_phone_number, - user_type: qParams.user_type, - district_id: qParams.district_id, - }; - if (qParams.district_id) { - const dis = await dispatch(getDistrict(qParams.district_id)); - if (!status.aborted) { - if (dis && dis.data) { - setDistrictName(dis.data.name); - } - } - } else { - setDistrictName(undefined); - } - const res = await dispatch(getUserList(params)); - if (!status.aborted) { - if (res && res.data) { - setUsers(res.data.results); - setTotalCount(res.data.count); - } - setIsLoading(false); - } + const { + data: userListData, + loading: userListLoading, + refetch: refetchUserList, + } = useQuery(routes.userList, { + query: { + limit: resultsPerPage.toString(), + offset: ( + (qParams.page ? qParams.page - 1 : 0) * resultsPerPage + ).toString(), + username: qParams.username, + first_name: qParams.first_name, + last_name: qParams.last_name, + phone_number: qParams.phone_number, + alt_phone_number: qParams.alt_phone_number, + user_type: qParams.user_type, + district_id: qParams.district_id, }, - [ - resultsPerPage, - qParams.page, - qParams.username, - qParams.first_name, - qParams.last_name, - qParams.phone_number, - qParams.alt_phone_number, - qParams.user_type, - qParams.district_id, - dispatch, - ] - ); + }); - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] + const { data: districtData, loading: districtDataLoading } = useQuery( + routes.getDistrict, + { + prefetch: !!qParams.district_id, + pathParams: { id: qParams.district_id }, + } ); const addUser = ( @@ -150,17 +109,15 @@ export default function ManageUsers() { const handleWorkingHourSubmit = async () => { const username = selectedUser; - if (!username || !weeklyHours || weeklyHours < 0 || weeklyHours > 168) { + if (!username || !weeklyHours || +weeklyHours < 0 || +weeklyHours > 168) { setWeeklyHoursError("Value should be between 0 and 168"); return; } - const res = await dispatch( - partialUpdateUser(username, { - weekly_working_hours: weeklyHours, - }) - ); - - if (res?.data) { + const { res, data, error } = await request(routes.partialUpdateUser, { + pathParams: { username }, + body: { weekly_working_hours: weeklyHours }, + }); + if (res && res.status === 200 && data) { Notification.Success({ msg: "Working hours updated successfully", }); @@ -168,29 +125,30 @@ export default function ManageUsers() { setSelectedUser(null); } else { Notification.Error({ - msg: "Error while updating working hours: " + (res.data.detail || ""), + msg: "Error while updating working hours: " + (error || ""), }); } - setWeeklyHours(0); + setWeeklyHours("0"); setWeeklyHoursError(""); - fetchData({ aborted: false }); + await refetchUserList(); }; const handleSubmit = async () => { - const username = userData.username; - const res = await dispatch(deleteUser(username)); + const { res, error } = await request(routes.deleteUser, { + pathParams: { username: userData.username }, + }); if (res?.status === 204) { Notification.Success({ msg: "User deleted successfully", }); } else { Notification.Error({ - msg: "Error while deleting User: " + (res?.data?.detail || ""), + msg: "Error while deleting User: " + (error || ""), }); } setUserData({ show: false, username: "", name: "" }); - fetchData({ aborted: false }); + await refetchUserList(); }; const handleDelete = (user: any) => { @@ -214,9 +172,9 @@ export default function ManageUsers() { let userList: any[] = []; - users && - users.length && - (userList = users.map((user: any, idx) => { + userListData?.results && + userListData.results.length && + (userList = userListData.results.map((user: any, idx) => { const cur_online = isUserOnline(user); return (
; - } else if (users?.length) { + } else if (userListData?.results.length) { manageUsers = (
{userList}
- +
); - } else if (users && users.length === 0) { + } else if (userListData?.results && userListData?.results.length === 0) { manageUsers = (
No Users Found
@@ -505,7 +463,7 @@ export default function ManageUsers() { open={expandWorkingHours} setOpen={(state) => { setExpandWorkingHours(state); - setWeeklyHours(0); + setWeeklyHours("0"); setWeeklyHoursError(""); }} slideFrom="right" @@ -539,8 +497,8 @@ export default function ManageUsers() {
@@ -574,7 +532,11 @@ export default function ManageUsers() { phoneNumber(), phoneNumber("WhatsApp no.", "alt_phone_number"), badge("Role", "user_type"), - value("District", "district_id", districtName || ""), + value( + "District", + "district_id", + qParams.district_id ? districtData?.name || "" : "" + ), ]} />
@@ -596,8 +558,6 @@ export default function ManageUsers() { function UserFacilities(props: { user: any }) { const { user } = props; const username = user.username; - const dispatch: any = useDispatch(); - const [facilities, setFacilities] = useState([]); const [isLoading, setIsLoading] = useState(false); const [facility, setFacility] = useState(null); const [unlinkFacilityData, setUnlinkFacilityData] = useState<{ @@ -635,62 +595,59 @@ function UserFacilities(props: { user: any }) { }); }; - const fetchFacilities = async () => { - setIsLoading(true); - const res = await dispatch(getUserListFacility({ username })); - if (res && res.data) { - setFacilities(res.data); - } - setIsLoading(false); - }; + const { + data: userFacilities, + loading: userFacilitiesLoading, + refetch: refetchUserFacilities, + } = useQuery(routes.userListFacility, { + pathParams: { username }, + }); const updateHomeFacility = async (username: string, facility: any) => { setIsLoading(true); - const res = await dispatch( - partialUpdateUser(username, { home_facility: facility.id }) - ); + const { res } = await request(routes.partialUpdateUser, { + pathParams: { username }, + body: { home_facility: facility.id.toString() }, + }); if (res && res.status === 200) user.home_facility_object = facility; - fetchFacilities(); + await refetchUserFacilities(); setIsLoading(false); }; const handleUnlinkFacilitySubmit = async () => { setIsLoading(true); if (unlinkFacilityData.isHomeFacility) { - const res = await dispatch( - clearHomeFacility(unlinkFacilityData.userName) - ); + const { res } = await request(routes.clearHomeFacility, { + pathParams: { username }, + }); if (res && res.status === 204) user.home_facility_object = null; } else { - await dispatch( - deleteUserFacility( - unlinkFacilityData.userName, - String(unlinkFacilityData?.facility?.id) - ) - ); + await request(routes.deleteUserFacility, { + pathParams: { username }, + body: { facility: unlinkFacilityData?.facility?.id?.toString() }, + }); } - fetchFacilities(); - setIsLoading(false); + await refetchUserFacilities(); hideUnlinkFacilityModal(); + setIsLoading(false); }; const addFacility = async (username: string, facility: any) => { setIsLoading(true); - const res = await dispatch(addUserFacility(username, String(facility.id))); + const { res } = await request(routes.addUserFacility, { + pathParams: { username }, + body: { facility: facility.id.toString() }, + }); if (res?.status !== 201) { Notification.Error({ msg: "Error while linking facility", }); } + await refetchUserFacilities(); setIsLoading(false); setFacility(null); - fetchFacilities(); }; - useEffect(() => { - fetchFacilities(); - }, []); - return (
{unlinkFacilityData.show && ( @@ -723,7 +680,7 @@ function UserFacilities(props: { user: any }) { Add
- {isLoading ? ( + {isLoading || userFacilitiesLoading ? (
@@ -761,13 +718,13 @@ function UserFacilities(props: { user: any }) { )} {/* Linked Facilities section */} - {facilities.length > 0 && ( + {userFacilities?.length && (
Linked Facilities
- {facilities.map((facility: any, i: number) => { + {userFacilities.map((facility: any, i: number) => { if (user?.home_facility_object?.id === facility.id) { // skip if it's a home facility return null; @@ -831,7 +788,7 @@ function UserFacilities(props: { user: any }) {
)} - {!user?.home_facility_object && facilities.length === 0 && ( + {!user?.home_facility_object && !userFacilities?.length && (
{ /* added const {t} hook here and relevant text to Common.json to avoid eslint error */ const { t } = useTranslation(); - const [skills, setSkills] = useState([]); const [selectedSkill, setSelectedSkill] = useState( null ); const [isLoading, setIsLoading] = useState(false); const [deleteSkill, setDeleteSkill] = useState(null); - const dispatch: any = useDispatch(); - const fetchSkills = useCallback( - async (username: string) => { - setIsLoading(true); - const res = await dispatch(getUserListSkills({ username })); - if (res && res.data) { - setSkills(res.data.results); - } - setIsLoading(false); - }, - [dispatch] - ); + const { + data: skills, + loading: skillsLoading, + refetch: refetchUserSkills, + } = useQuery(routes.userListSkill, { + pathParams: { username }, + }); const addSkill = useCallback( async (username: string, skill: SkillObjectModel | null) => { if (!skill) return; setIsLoading(true); - const res = await dispatch(addUserSkill(username, skill.id)); - if (res?.status !== 201) { + const { res } = await request(routes.addUserSkill, { + pathParams: { username }, + body: { skill: skill.id }, + }); + if (!res?.ok) { Notification.Error({ msg: "Error while adding skill", }); @@ -62,36 +56,32 @@ export default ({ show, setShow, username }: IProps) => { } setSelectedSkill(null); setIsLoading(false); - fetchSkills(username); + await refetchUserSkills(); }, - [dispatch, fetchSkills] + [refetchUserSkills] ); const removeSkill = useCallback( async (username: string, skillId: string) => { - const res = await dispatch(deleteUserSkill(username, skillId)); + const { res } = await request(routes.deleteUserSkill, { + pathParams: { username, id: skillId }, + }); if (res?.status !== 204) { Notification.Error({ msg: "Error while unlinking skill", }); } setDeleteSkill(null); - fetchSkills(username); + await refetchUserSkills(); }, - [dispatch, fetchSkills] + [refetchUserSkills] ); - useEffect(() => { - setIsLoading(true); - if (username) fetchSkills(username); - setIsLoading(false); - }, [username, fetchSkills]); - const authorizeForAddSkill = useIsAuthorized( AuthorizeFor(["DistrictAdmin", "StateAdmin"]) ); - const hasSkills = useMemo(() => skills.length > 0, [skills]); + const hasSkills = skills?.results?.length || 0 > 0; return (
@@ -114,7 +104,7 @@ export default ({ show, setShow, username }: IProps) => { >
- {!isLoading && ( + {(!isLoading || !skillsLoading) && (
{ setSelected={setSelectedSkill} errors="" username={username} + userSkills={skills?.results || []} /> { )}
)} - {isLoading ? ( + {isLoading || skillsLoading ? (
@@ -151,8 +142,8 @@ export default ({ show, setShow, username }: IProps) => {
{hasSkills ? ( diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx index a6553bad01b..7df0089cdac 100644 --- a/src/Components/Users/UserAdd.tsx +++ b/src/Components/Users/UserAdd.tsx @@ -1,26 +1,17 @@ import { Link, navigate } from "raviger"; -import { lazy, useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useEffect, useState } from "react"; import { GENDER_TYPES, USER_TYPES, USER_TYPE_OPTIONS, } from "../../Common/constants"; -import { statusType, useAbortableEffect } from "../../Common/utils"; +import { useAbortableEffect } from "../../Common/utils"; import { validateEmailAddress, validateName, validatePassword, validateUsername, } from "../../Common/validation"; -import { - addUser, - getDistrictByState, - getLocalbodyByDistrict, - getStates, - getUserListFacility, - checkUsername, -} from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FacilityModel } from "../Facility/models"; @@ -45,6 +36,9 @@ import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; import dayjs from "../../Utils/dayjs"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { PhoneNumberValidator } from "../Form/FieldValidators"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); @@ -163,7 +157,6 @@ export const validateRule = ( export const UserAdd = (props: UserProps) => { const { goBack } = useAppHistory(); - const dispatchAction: any = useDispatch(); const { userId } = props; const [state, dispatch] = useAutoSaveReducer( @@ -171,13 +164,9 @@ export const UserAdd = (props: UserProps) => { initialState ); const [isLoading, setIsLoading] = useState(false); - const [isStateLoading, setIsStateLoading] = useState(false); - const [isDistrictLoading, setIsDistrictLoading] = useState(false); - const [isLocalbodyLoading, setIsLocalbodyLoading] = useState(false); - const [_current_user_facilities, setFacilities] = useState< - Array - >([]); const [states, setStates] = useState([]); + const [selectedStateId, setSelectedStateId] = useState(0); + const [selectedDistrictId, setSelectedDistrictId] = useState(0); const [districts, setDistricts] = useState([]); const [localBodies, setLocalBodies] = useState([]); const [selectedFacility, setSelectedFacility] = useState([]); @@ -198,9 +187,9 @@ export const UserAdd = (props: UserProps) => { const check_username = async (username: string) => { setUsernameExists(userExistsEnums.checking); - const usernameCheck = await dispatchAction( - checkUsername({ username: username }) - ); + const { res: usernameCheck } = await request(routes.checkUsername, { + pathParams: { username }, + }); if (usernameCheck === undefined || usernameCheck.status === 409) setUsernameExists(userExistsEnums.exists); else if (usernameCheck.status === 200) @@ -254,101 +243,45 @@ export const UserAdd = (props: UserProps) => { state.form.user_type === "StaffReadOnly" ); - const fetchDistricts = useCallback( - async (id: number) => { - if (id > 0) { - setIsDistrictLoading(true); - const districtList = await dispatchAction(getDistrictByState({ id })); - if (districtList) { - if (userIndex <= USER_TYPES.indexOf("DistrictAdmin")) { - setDistricts([ - { - id: authUser.district!, - name: authUser.district_object?.name as string, - }, - ]); - } else { - setDistricts(districtList.data); - } - } - setIsDistrictLoading(false); + const { loading: isDistrictLoading } = useQuery(routes.getDistrictByState, { + prefetch: !!(selectedStateId > 0), + pathParams: { id: selectedStateId.toString() }, + onResponse: (result) => { + if (!result || !result.res || !result.data) return; + if (userIndex <= USER_TYPES.indexOf("DistrictAdmin")) { + setDistricts([authUser.district_object!]); + } else { + setDistricts(result.data); } }, - [dispatchAction] - ); - - const fetchLocalBody = useCallback( - async (id: number) => { - if (id > 0) { - setIsLocalbodyLoading(true); - const localBodyList = await dispatchAction( - getLocalbodyByDistrict({ id }) - ); - setIsLocalbodyLoading(false); - if (localBodyList) { - if (userIndex <= USER_TYPES.indexOf("LocalBodyAdmin")) { - setLocalBodies([ - { - id: authUser.local_body!, - name: authUser.local_body_object?.name as string, - }, - ]); - } else { - setLocalBodies(localBodyList.data); - } - } - } - }, - [dispatchAction] - ); - - const fetchStates = useCallback( - async (status: statusType) => { - setIsStateLoading(true); - const statesRes = await dispatchAction(getStates()); - if (!status.aborted && statesRes.data.results) { - if (userIndex <= USER_TYPES.indexOf("StateAdmin")) { - setStates([ - { - id: authUser.state!, - name: authUser.state_object?.name as string, - }, - ]); + }); + + const { loading: isLocalbodyLoading } = useQuery( + routes.getAllLocalBodyByDistrict, + { + prefetch: !!(selectedDistrictId > 0), + pathParams: { id: selectedDistrictId.toString() }, + onResponse: (result) => { + if (!result || !result.res || !result.data) return; + if (userIndex <= USER_TYPES.indexOf("LocalBodyAdmin")) { + setLocalBodies([authUser.local_body_object!]); } else { - setStates(statesRes.data.results); + setLocalBodies(result.data); } - } - setIsStateLoading(false); - }, - [dispatchAction] - ); - - const fetchFacilities = useCallback( - async (status: any) => { - setIsStateLoading(true); - const res = await dispatchAction( - getUserListFacility({ username: authUser.username }) - ); - if (!status.aborted && res && res.data) { - setFacilities(res.data); - } - setIsStateLoading(false); - }, - [dispatchAction, authUser.username] + }, + } ); - useAbortableEffect( - (status: statusType) => { - fetchStates(status); - if ( - authUser.user_type === "Staff" || - authUser.user_type === "StaffReadOnly" - ) { - fetchFacilities(status); + const { loading: isStateLoading } = useQuery(routes.statesList, { + onResponse: (result) => { + if (!result || !result.res || !result.data) return; + if (userIndex <= USER_TYPES.indexOf("StateAdmin")) { + setStates([authUser.state_object!]); + } else { + setStates(result.data.results); } }, - [dispatch] - ); + }); const handleDateChange = (e: FieldChangeEvent) => { if (dayjs(e.value).isValid()) { @@ -605,13 +538,10 @@ export const UserAdd = (props: UserProps) => { : undefined, }; - const res = await dispatchAction(addUser(data)); - if ( - res && - (res.data || res.data === "") && - res.status >= 200 && - res.status < 300 - ) { + const { res } = await request(routes.addUser, { + body: data, + }); + if (res?.ok) { dispatch({ type: "set_form", form: initForm }); if (!userId) { Notification.Success({ @@ -916,7 +846,7 @@ export const UserAdd = (props: UserProps) => { optionValue={(o) => o.id} onChange={(e) => { handleFieldChange(e); - if (e) fetchDistricts(e.value); + if (e) setSelectedStateId(e.value); }} /> )} @@ -934,7 +864,7 @@ export const UserAdd = (props: UserProps) => { optionValue={(o) => o.id} onChange={(e) => { handleFieldChange(e); - if (e) fetchLocalBody(e.value); + if (e) setSelectedDistrictId(e.value); }} /> )} diff --git a/src/Components/Users/UserFilter.tsx b/src/Components/Users/UserFilter.tsx index 3dca52d2463..4544fb8893a 100644 --- a/src/Components/Users/UserFilter.tsx +++ b/src/Components/Users/UserFilter.tsx @@ -1,6 +1,3 @@ -import { useEffect } from "react"; -import { useDispatch } from "react-redux"; -import { getDistrict } from "../../Redux/actions"; import { navigate } from "raviger"; import DistrictSelect from "../Facility/FacilityFilter/DistrictSelect"; import { parsePhoneNumber } from "../../Utils/utils"; @@ -11,6 +8,8 @@ import { USER_TYPE_OPTIONS } from "../../Common/constants"; import useMergeState from "../../Common/hooks/useMergeState"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; const parsePhoneNumberForFilterParam = (phoneNumber: string) => { if (!phoneNumber) return ""; @@ -21,7 +20,6 @@ const parsePhoneNumberForFilterParam = (phoneNumber: string) => { export default function UserFilter(props: any) { const { filter, onChange, closeFilter } = props; - const dispatch: any = useDispatch(); const [filterState, setFilterState] = useMergeState({ first_name: filter.first_name || "", last_name: filter.last_name || "", @@ -69,17 +67,14 @@ export default function UserFilter(props: any) { onChange(data); }; - useEffect(() => { - async function fetchData() { - if (filter.district_id) { - const { data: districtData } = await dispatch( - getDistrict(filter.district_id, "district") - ); - setFilterState({ district_ref: districtData }); - } - } - fetchData(); - }, [dispatch]); + useQuery(routes.getDistrict, { + prefetch: !!filter.district_id, + pathParams: { id: filter.district_id }, + onResponse: (result) => { + if (!result || !result.data || !result.res) return; + setFilterState({ district_ref: result.data }); + }, + }); const handleChange = ({ name, value }: any) => setFilterState({ ...filterState, [name]: value }); diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx index 441a6862634..76a94745c1a 100644 --- a/src/Components/Users/UserProfile.tsx +++ b/src/Components/Users/UserProfile.tsx @@ -1,13 +1,5 @@ -import { useState, useCallback, useReducer, lazy, FormEvent } from "react"; -import { statusType, useAbortableEffect } from "../../Common/utils"; +import { useState, useReducer, lazy, FormEvent } from "react"; import { GENDER_TYPES } from "../../Common/constants"; -import { useDispatch } from "react-redux"; -import { - getUserDetails, - getUserListSkills, - partialUpdateUser, - updateUserPassword, -} from "../../Redux/actions"; import { validateEmailAddress } from "../../Common/validation"; import * as Notification from "../../Utils/Notifications.js"; import LanguageSelector from "../../Components/Common/LanguageSelector"; @@ -18,15 +10,32 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import { SkillModel, SkillObjectModel } from "../Users/models"; +import { GenderType, SkillModel, UpdatePasswordForm } from "../Users/models"; import UpdatableApp, { checkForUpdate } from "../Common/UpdatableApp"; import dayjs from "../../Utils/dayjs"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { PhoneNumberValidator } from "../Form/FieldValidators"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); type EditForm = { + firstName: string; + lastName: string; + age: string; + gender: GenderType; + email: string; + phoneNumber: string; + altPhoneNumber: string; + user_type: string | undefined; + doctor_qualification: string | undefined; + doctor_experience_commenced_on: number | string | undefined; + doctor_medical_council_registration: string | undefined; + weekly_working_hours: string | undefined; +}; +type ErrorForm = { firstName: string; lastName: string; age: string; @@ -34,6 +43,7 @@ type EditForm = { email: string; phoneNumber: string; altPhoneNumber: string; + user_type: string | undefined; doctor_qualification: string | undefined; doctor_experience_commenced_on: number | string | undefined; doctor_medical_council_registration: string | undefined; @@ -41,27 +51,28 @@ type EditForm = { }; type State = { form: EditForm; - errors: EditForm; + errors: ErrorForm; }; type Action = | { type: "set_form"; form: EditForm } - | { type: "set_error"; errors: EditForm }; + | { type: "set_error"; errors: ErrorForm }; const initForm: EditForm = { firstName: "", lastName: "", age: "", - gender: "", + gender: "Male", email: "", phoneNumber: "", altPhoneNumber: "", + user_type: "", doctor_qualification: undefined, doctor_experience_commenced_on: undefined, doctor_medical_council_registration: undefined, weekly_working_hours: undefined, }; -const initError: EditForm = Object.assign( +const initError: ErrorForm = Object.assign( {}, ...Object.keys(initForm).map((k) => ({ [k]: "" })) ); @@ -87,9 +98,9 @@ const editFormReducer = (state: State, action: Action) => { } } }; + export default function UserProfile() { const [states, dispatch] = useReducer(editFormReducer, initialState); - const reduxDispatch: any = useDispatch(); const [updateStatus, setUpdateStatus] = useState({ isChecking: false, isUpdateAvailable: false, @@ -119,57 +130,44 @@ export default function UserProfile() { const [showEdit, setShowEdit] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const dispatchAction: any = useDispatch(); - - const initialDetails: any = [{}]; - const [details, setDetails] = useState(initialDetails); - - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatchAction(getUserDetails(authUser.username)); - const resSkills = await dispatchAction( - getUserListSkills({ username: authUser.username }) - ); - if (!status.aborted) { - if (res && res.data && resSkills) { - res.data.skills = resSkills.data.results.map( - (skill: SkillModel) => skill.skill_object - ); - setDetails(res.data); - const formData: EditForm = { - firstName: res.data.first_name, - lastName: res.data.last_name, - age: res.data.age, - gender: res.data.gender, - email: res.data.email, - phoneNumber: res.data.phone_number, - altPhoneNumber: res.data.alt_phone_number, - doctor_qualification: res.data.doctor_qualification, - doctor_experience_commenced_on: dayjs().diff( - dayjs(res.data.doctor_experience_commenced_on), - "years" - ), - doctor_medical_council_registration: - res.data.doctor_medical_council_registration, - weekly_working_hours: res.data.weekly_working_hours, - }; - dispatch({ - type: "set_form", - form: formData, - }); - } - setIsLoading(false); - } - }, - [dispatchAction, authUser.username] - ); - useAbortableEffect( - (status: statusType) => { - fetchData(status); + const { + data: userData, + loading: isUserLoading, + refetch: refetchUserData, + } = useQuery(routes.getUserDetails, { + pathParams: { username: authUser.username }, + onResponse: (result) => { + if (!result || !result.res || !result.data) return; + const formData: EditForm = { + firstName: result.data.first_name, + lastName: result.data.last_name, + age: result.data.age?.toString() || "", + gender: result.data.gender || "Male", + email: result.data.email, + phoneNumber: result.data.phone_number?.toString() || "", + altPhoneNumber: result.data.alt_phone_number?.toString() || "", + user_type: result.data.user_type, + doctor_qualification: result.data.doctor_qualification, + doctor_experience_commenced_on: dayjs().diff( + dayjs(result.data.doctor_experience_commenced_on), + "years" + ), + doctor_medical_council_registration: + result.data.doctor_medical_council_registration, + weekly_working_hours: result.data.weekly_working_hours, + }; + dispatch({ + type: "set_form", + form: formData, + }); }, - [fetchData] + }); + + const { data: skillsView, loading: isSkillsLoading } = useQuery( + routes.userListSkill, + { + pathParams: { username: authUser.username }, + } ); const validateForm = () => { @@ -244,7 +242,7 @@ export default function UserProfile() { case "doctor_qualification": case "doctor_experience_commenced_on": case "doctor_medical_council_registration": - if (details.user_type === "Doctor" && !states.form[field]) { + if (states.form.user_type === "Doctor" && !states.form[field]) { errors[field] = "Field is required"; invalidForm = true; } @@ -298,13 +296,13 @@ export default function UserProfile() { phone_number: parsePhoneNumber(states.form.phoneNumber) ?? "", alt_phone_number: parsePhoneNumber(states.form.altPhoneNumber) ?? "", gender: states.form.gender, - age: states.form.age, + age: +states.form.age, doctor_qualification: - details.user_type === "Doctor" + states.form.user_type === "Doctor" ? states.form.doctor_qualification : undefined, doctor_experience_commenced_on: - details.user_type === "Doctor" + states.form.user_type === "Doctor" ? dayjs() .subtract( parseInt( @@ -316,34 +314,27 @@ export default function UserProfile() { .format("YYYY-MM-DD") : undefined, doctor_medical_council_registration: - details.user_type === "Doctor" + states.form.user_type === "Doctor" ? states.form.doctor_medical_council_registration : undefined, weekly_working_hours: states.form.weekly_working_hours, }; - const res = await dispatchAction( - partialUpdateUser(authUser.username, data) - ); - if (res && res.data) { + const { res } = await request(routes.partialUpdateUser, { + pathParams: { username: authUser.username }, + body: data, + }); + if (res?.ok) { Notification.Success({ msg: "Details updated successfully", }); - window.location.reload(); - setDetails({ - ...details, - first_name: states.form.firstName, - last_name: states.form.lastName, - age: states.form.age, - gender: states.form.gender, - email: states.form.email, - phone_number: states.form.phoneNumber, - alt_phone_number: states.form.altPhoneNumber, - }); + await refetchUserData(); setShowEdit(false); } } }; + const isLoading = isUserLoading || isSkillsLoading; + if (isLoading) { return ; } @@ -367,7 +358,7 @@ export default function UserProfile() { } }; - const changePassword = (e: any) => { + const changePassword = async (e: any) => { e.preventDefault(); //validating form if ( @@ -377,30 +368,28 @@ export default function UserProfile() { msg: "Passwords are different in the new and the confirmation column.", }); } else { - setIsLoading(true); - const form = { + const form: UpdatePasswordForm = { old_password: changePasswordForm.old_password, username: authUser.username, new_password: changePasswordForm.new_password_1, }; - reduxDispatch(updateUserPassword(form)).then((resp: any) => { - setIsLoading(false); - const res = resp && resp.data; - if (res.message === "Password updated successfully") { - Notification.Success({ - msg: "Password changed!", - }); - } else { - Notification.Error({ - msg: "There was some error. Please try again in some time.", - }); - } - setChangePasswordForm({ - ...changePasswordForm, - new_password_1: "", - new_password_2: "", - old_password: "", + const { res, data } = await request(routes.updatePassword, { + body: form, + }); + if (res?.ok && data?.message === "Password updated successfully") { + Notification.Success({ + msg: "Password changed!", + }); + } else { + Notification.Error({ + msg: "There was some error. Please try again in some time.", }); + } + setChangePasswordForm({ + ...changePasswordForm, + new_password_1: "", + new_password_2: "", + old_password: "", }); } }; @@ -432,7 +421,7 @@ export default function UserProfile() {
- {!showEdit && ( + {!showEdit && !isLoading && (
- {details.username || "-"} + {userData?.username || "-"}
- {details.phone_number || "-"} + {userData?.phone_number || "-"}
@@ -466,7 +455,7 @@ export default function UserProfile() { Whatsapp No
- {details.alt_phone_number || "-"} + {userData?.alt_phone_number || "-"}
- {details.email || "-"} + {userData?.email || "-"}
- {details.first_name || "-"} + {userData?.first_name || "-"}
- {details.last_name || "-"} + {userData?.last_name || "-"}
@@ -507,7 +496,7 @@ export default function UserProfile() { Age
- {details.age || "-"} + {userData?.age || "-"}
@@ -516,7 +505,7 @@ export default function UserProfile() {
{" "} - {details.user_type || "-"} + {userData?.user_type || "-"}
- {details.gender || "-"} + {userData?.gender || "-"}
@@ -535,7 +524,7 @@ export default function UserProfile() { Local Body
- {details.local_body_object?.name || "-"} + {userData?.local_body_object?.name || "-"}
@@ -543,7 +532,7 @@ export default function UserProfile() { District
- {details.district_object?.name || "-"} + {userData?.district_object?.name || "-"}
@@ -551,7 +540,7 @@ export default function UserProfile() { State
- {details.state_object?.name || "-"} + {userData?.state_object?.name || "-"}
@@ -563,11 +552,13 @@ export default function UserProfile() { className="flex flex-wrap gap-2" id="already-linked-skills" > - {details.skills && details.skills.length - ? details.skills?.map((skill: SkillObjectModel) => { + {skillsView?.results?.length + ? skillsView.results?.map((skill: SkillModel) => { return ( -

{skill.name}

+

+ {skill.skill_object.name} +

); }) @@ -583,7 +574,7 @@ export default function UserProfile() { Average weekly working hours
- {details.weekly_working_hours ?? "-"} + {userData?.weekly_working_hours || "-"}
@@ -649,7 +640,7 @@ export default function UserProfile() { required type="email" /> - {details.user_type === "Doctor" && ( + {states.form.user_type === "Doctor" && ( <> { export const signupUser = (params: object) => { return fireRequest("createUser", [], params); }; -export const addUser = (params: object) => { - return fireRequest("addUser", [], params); -}; export const deleteUser = (username: string) => { - return fireRequest("deleteUser", [username], {}); + return fireRequest("deleteUser", [], {}, { username }); }; export const checkResetToken = (params: object) => { @@ -34,10 +31,6 @@ export const postForgotPassword = (form: object) => { return fireRequest("forgotPassword", [], form); }; -export const updateUserPassword = (form: object) => { - return fireRequest("updatePassword", [], form); -}; - export const getUserPnconfig = (pathParams: object) => { return fireRequest("getUserPnconfig", [], {}, pathParams); }; @@ -62,14 +55,6 @@ export const deleteFacilityCoverImage = (id: string) => { export const getUserList = (params: object, key?: string) => { return fireRequest("userList", [], params, null, key); }; - -export const getUserListSkills = (pathParam: object) => { - return fireRequest("userListSkill", [], {}, pathParam); -}; - -export const partialUpdateUser = (username: string, data: any) => { - return fireRequest("partialUpdateUser", [], data, { username }); -}; export const getUserListFacility = (pathParam: object) => { return fireRequest("userListFacility", [], {}, pathParam); }; @@ -95,10 +80,6 @@ export const deleteUserFacility = (username: string, facility: string) => { ); }; -export const clearHomeFacility = (username: string) => { - return fireRequest("clearHomeFacility", [], {}, { username }); -}; - export const getPermittedFacilities = (params: object) => { return fireRequest("getPermittedFacilities", [], params); }; @@ -605,20 +586,6 @@ export const dischargePatient = (params: object, pathParams: object) => { //Profile -export const checkUsername = (params: object) => { - return fireRequest("checkUsername", [], {}, params, undefined, true); -}; - -export const getUserDetails = (username: string, suppress?: boolean) => { - return fireRequest( - "getUserDetails", - [], - {}, - { username: username }, - undefined, - suppress ?? true - ); -}; export const updateUserDetails = (username: string, data: object) => { return fireRequest("updateUserDetails", [username], data); }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 4172effe432..2a64d921792 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -33,6 +33,7 @@ import { ConsultationModel, CreateBedBody, CurrentBed, + DistrictModel, DailyRoundsBody, DailyRoundsRes, DoctorModal, @@ -40,9 +41,10 @@ import { IFacilityNotificationRequest, IFacilityNotificationResponse, IUserFacilityRequest, - LocationModel, PatientStatsModel, WardModel, + LocationModel, + StateModel, } from "../Components/Facility/models"; import { IDeleteExternalResult, @@ -52,9 +54,13 @@ import { ILocalBodyByDistrict, IPartialUpdateExternalResult, } from "../Components/ExternalResult/models"; - +import { + SkillModel, + SkillObjectModel, + UpdatePasswordForm, + UserModel, +} from "../Components/Users/models"; import { Prescription } from "../Components/Medicine/models"; -import { UserModel } from "../Components/Users/models"; import { DailyRoundsModel, PatientModel } from "../Components/Patient/models"; import { PaginatedResponse } from "../Utils/request/types"; import { @@ -149,6 +155,8 @@ const routes = { updatePassword: { path: "/api/v1/password_change/", method: "PUT", + TRes: Type>(), + TBody: Type(), }, // User Endpoints currentUser: { @@ -164,11 +172,14 @@ const routes = { userListSkill: { path: "/api/v1/users/{username}/skill/", + method: "GET", + TRes: Type>(), }, userListFacility: { path: "/api/v1/users/{username}/get_facilities/", - TRes: Type(), + method: "GET", + TRes: Type(), }, addUserFacility: { @@ -181,6 +192,8 @@ const routes = { addUserSkill: { path: "/api/v1/users/{username}/skill/", method: "POST", + TBody: Type<{ skill: string }>(), + TRes: Type(), }, deleteUserFacility: { @@ -193,11 +206,13 @@ const routes = { clearHomeFacility: { path: "/api/v1/users/{username}/clear_home_facility/", method: "DELETE", + TRes: Type>(), }, deleteUserSkill: { path: "/api/v1/users/{username}/skill/{id}/", method: "DELETE", + TRes: Type>(), }, createUser: { @@ -214,6 +229,8 @@ const routes = { partialUpdateUser: { path: "/api/v1/users/{username}/", method: "PATCH", + TRes: Type(), + TBody: Type>(), }, deleteUser: { @@ -225,6 +242,7 @@ const routes = { addUser: { path: "/api/v1/users/add_user/", method: "POST", + TRes: Type(), }, searchUser: { @@ -251,6 +269,8 @@ const routes = { getAllSkills: { path: "/api/v1/skill/", + method: "GET", + TRes: Type>(), }, // Facility Endpoints @@ -651,6 +671,8 @@ const routes = { // States statesList: { path: "/api/v1/state/", + method: "GET", + TRes: Type>(), }, getState: { @@ -661,9 +683,13 @@ const routes = { getDistrict: { path: "/api/v1/district/{id}/", + method: "GET", + TRes: Type(), }, getDistrictByState: { path: "/api/v1/state/{id}/districts/", + method: "GET", + TRes: Type(), }, getDistrictByName: { path: "/api/v1/district/", @@ -775,11 +801,13 @@ const routes = { checkUsername: { path: "/api/v1/users/{username}/check_availability/", method: "GET", + TRes: Type>(), }, getUserDetails: { path: "/api/v1/users/{username}/", method: "GET", + TRes: Type(), }, updateUserDetails: { path: "/api/v1/users",