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 cdc7074c145..536cc044049 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 1450e6b6f79..83b51e5c47d 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",