From f7b8283decd380a4c5acfd596e9ffe8ef3c7d537 Mon Sep 17 00:00:00 2001 From: Srijan Tripathi <72181144+srijantrpth@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:23:10 +0530 Subject: [PATCH 1/8] Added Custom Headers Logic to Query (#9458) --------- Co-authored-by: rithviknishad --- src/Utils/request/query.ts | 2 +- src/Utils/request/types.ts | 1 + src/Utils/request/utils.ts | 24 +++++++----------------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Utils/request/query.ts b/src/Utils/request/query.ts index 3431f625728..53fe96878a2 100644 --- a/src/Utils/request/query.ts +++ b/src/Utils/request/query.ts @@ -13,7 +13,7 @@ async function queryRequest( const fetchOptions: RequestInit = { method, - headers: makeHeaders(noAuth ?? false), + headers: makeHeaders(noAuth ?? false, options?.headers), signal: options?.signal, }; diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 20f095ae6fd..2b4be31d28d 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -41,6 +41,7 @@ export interface QueryOptions { body?: TBody; silent?: boolean; signal?: AbortSignal; + headers?: HeadersInit; } export interface PaginatedResponse { diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index 8fd7bc96bea..03427c17b44 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -50,33 +50,23 @@ const ensurePathNotMissingReplacements = (path: string) => { } }; -export function makeHeaders(noAuth: boolean) { - const headers = new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }); +export function makeHeaders(noAuth: boolean, additionalHeaders?: HeadersInit) { + const headers = new Headers(additionalHeaders); + + headers.set("Content-Type", "application/json"); + headers.append("Accept", "application/json"); if (!noAuth) { - const token = getAuthorizationHeader(); + const token = localStorage.getItem(LocalStorageKeys.accessToken); if (token) { - headers.append("Authorization", token); + headers.append("Authorization", `Bearer ${token}`); } } return headers; } -export function getAuthorizationHeader() { - const bearerToken = localStorage.getItem(LocalStorageKeys.accessToken); - - if (bearerToken) { - return `Bearer ${bearerToken}`; - } - - return null; -} - export function mergeRequestOptions( options: RequestOptions, overrides: RequestOptions, From 69527b8bc33a69a15b0c706218d905e9ec07e296 Mon Sep 17 00:00:00 2001 From: Mahendar Chikolla <119734520+Mahendar0701@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:55:04 +0530 Subject: [PATCH 2/8] Fix: Duplicate network requests in Patient Details page (#9297) --- src/components/Facility/PatientNotesList.tsx | 12 +----- .../Patient/PatientDetailsTab/Demography.tsx | 16 ++++--- .../PatientDetailsTab/EncounterHistory.tsx | 42 ++----------------- .../HealthProfileSummary.tsx | 6 +-- .../PatientDetailsTab/ImmunisationRecords.tsx | 7 ++-- .../Patient/PatientDetailsTab/Notes.tsx | 40 ++++-------------- .../PatientDetailsTab/ShiftingHistory.tsx | 5 +-- .../Patient/PatientDetailsTab/index.tsx | 15 +++---- src/components/Patient/PatientHome.tsx | 38 +++++++++-------- 9 files changed, 56 insertions(+), 125 deletions(-) diff --git a/src/components/Facility/PatientNotesList.tsx b/src/components/Facility/PatientNotesList.tsx index dcf84e8b708..8db68395744 100644 --- a/src/components/Facility/PatientNotesList.tsx +++ b/src/components/Facility/PatientNotesList.tsx @@ -58,18 +58,10 @@ const PatientNotesList = (props: PatientNotesProps) => { }; useEffect(() => { - if (reload) { + if (reload || thread) { fetchNotes(); } - }, [reload]); - - useEffect(() => { - fetchNotes(); - }, [thread]); - - useEffect(() => { - setReload(true); - }, []); + }, [reload, thread]); const handleNext = () => { if (state.cPage < state.totalPages) { diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx index e5bed5abfee..d6e1379cd7e 100644 --- a/src/components/Patient/PatientDetailsTab/Demography.tsx +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -9,21 +9,21 @@ import AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; import { Button } from "@/components/ui/button"; +import { InsuranceDetailsCard } from "@/components/Patient/InsuranceDetailsCard"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { parseOccupation } from "@/components/Patient/PatientHome"; +import { AssignedToObjectModel } from "@/components/Patient/models"; + import useAuthUser from "@/hooks/useAuthUser"; import { GENDER_TYPES } from "@/common/constants"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; import { formatName, formatPatientAge } from "@/Utils/utils"; -import { PatientProps } from "."; -import * as Notification from "../../../Utils/Notifications"; -import { InsuranceDetailsCard } from "../InsuranceDetailsCard"; -import { parseOccupation } from "../PatientHome"; -import { AssignedToObjectModel } from "../models"; - export const Demography = (props: PatientProps) => { const { patientData, facilityId, id } = props; const authUser = useAuthUser(); @@ -64,9 +64,7 @@ export const Demography = (props: PatientProps) => { const { data: insuranceDetials } = useTanStackQueryInstead( routes.hcx.policies.list, { - query: { - patient: id, - }, + query: { patient: id }, }, ); diff --git a/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx index b81009efa74..9ad679f8e9a 100644 --- a/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx +++ b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx @@ -1,56 +1,20 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import PaginatedList from "@/CAREUI/misc/PaginatedList"; import CircularProgress from "@/components/Common/CircularProgress"; -import Loading from "@/components/Common/Loading"; import { ConsultationCard } from "@/components/Facility/ConsultationCard"; import { ConsultationModel } from "@/components/Facility/models"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; -import useAuthUser from "@/hooks/useAuthUser"; - -import { triggerGoal } from "@/Integrations/Plausible"; import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -import { PatientProps } from "."; -import { PatientModel } from "../models"; const EncounterHistory = (props: PatientProps) => { - const { patientData: initialPatientData, facilityId, id } = props; - const [patientData, setPatientData] = - useState(initialPatientData); - const authUser = useAuthUser(); - - useEffect(() => { - setPatientData(initialPatientData); - }, [initialPatientData]); + const { patientData, id, refetch } = props; const { t } = useTranslation(); - const { loading: isLoading, refetch } = useTanStackQueryInstead( - routes.getPatient, - { - pathParams: { - id, - }, - onResponse: ({ res, data }) => { - if (res?.ok && data) { - setPatientData(data); - } - triggerGoal("Patient Profile Viewed", { - facilityId: facilityId, - userId: authUser.id, - }); - }, - }, - ); - - if (isLoading) { - return ; - } - return ( { const { patientData, facilityId, id } = props; diff --git a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx index 7ee828868fc..b16059abe46 100644 --- a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx +++ b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx @@ -5,18 +5,17 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { PatientModel } from "@/components/Patient/models"; import { UserModel } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; import { ADMIN_USER_TYPES } from "@/common/constants"; +import * as Notification from "@/Utils/Notifications"; import { formatDateTime } from "@/Utils/utils"; -import { PatientProps } from "."; -import * as Notification from "../../../Utils/Notifications"; -import { PatientModel } from "../models"; - export const ImmunisationRecords = (props: PatientProps) => { const { patientData, facilityId, id } = props; diff --git a/src/components/Patient/PatientDetailsTab/Notes.tsx b/src/components/Patient/PatientDetailsTab/Notes.tsx index 646e97d3bd5..4fccf7a1119 100644 --- a/src/components/Patient/PatientDetailsTab/Notes.tsx +++ b/src/components/Patient/PatientDetailsTab/Notes.tsx @@ -1,5 +1,5 @@ import { t } from "i18next"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -11,6 +11,7 @@ import { PatientNotesModel, } from "@/components/Facility/models"; import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; import useAuthUser from "@/hooks/useAuthUser"; import { useMessageListener } from "@/hooks/useMessageListener"; @@ -18,19 +19,13 @@ import { useMessageListener } from "@/hooks/useMessageListener"; import { PATIENT_NOTES_THREADS } from "@/common/constants"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import { classNames, keysOf } from "@/Utils/utils"; -import * as Notification from "../../../Utils/Notifications"; - -interface PatientNotesProps { - id: string; - facilityId: string; -} - -const PatientNotes = (props: PatientNotesProps) => { - const { id: patientId, facilityId } = props; +const PatientNotes = (props: PatientProps) => { + const { patientData, id: patientId, facilityId } = props; const authUser = useAuthUser(); const [thread, setThread] = useState( @@ -39,7 +34,6 @@ const PatientNotes = (props: PatientNotesProps) => { : PATIENT_NOTES_THREADS.Doctors, ); - const [patientActive, setPatientActive] = useState(true); const [noteField, setNoteField] = useState(""); const [reload, setReload] = useState(false); const [reply_to, setReplyTo] = useState( @@ -84,26 +78,6 @@ const PatientNotes = (props: PatientNotesProps) => { } }; - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - try { - const { data } = await request(routes.getPatient, { - pathParams: { id: patientId }, - }); - if (data) { - setPatientActive(data.is_active ?? true); - } - } catch (error) { - Notification.Error({ - msg: "Failed to fetch patient status", - }); - } - } - } - fetchPatientName(); - }, [patientId]); - useMessageListener((data) => { const message = data?.message; if ( @@ -161,7 +135,7 @@ const PatientNotes = (props: PatientNotesProps) => { errorClassName="hidden" innerClassName="pr-10" placeholder={t("notes_placeholder")} - disabled={!patientActive} + disabled={!patientData.is_active} /> { className="absolute right-2" ghost size="small" - disabled={!patientActive} + disabled={!patientData.is_active} authorizeFor={NonReadOnlyUsers} > diff --git a/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx index b9f63da5512..6bd1bb5bbb7 100644 --- a/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx +++ b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import ButtonV2 from "@/components/Common/ButtonV2"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { PatientModel } from "@/components/Patient/models"; import { formatFilter } from "@/components/Resource/ResourceCommons"; import ShiftingTable from "@/components/Shifting/ShiftingTable"; @@ -13,9 +15,6 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { PatientProps } from "."; -import { PatientModel } from "../models"; - const ShiftingHistory = (props: PatientProps) => { const { patientData, facilityId, id } = props; const { t } = useTranslation(); diff --git a/src/components/Patient/PatientDetailsTab/index.tsx b/src/components/Patient/PatientDetailsTab/index.tsx index 6f4b7ecc982..55c292438ca 100644 --- a/src/components/Patient/PatientDetailsTab/index.tsx +++ b/src/components/Patient/PatientDetailsTab/index.tsx @@ -1,15 +1,16 @@ -import { PatientModel } from "../models"; -import { Demography } from "./Demography"; -import EncounterHistory from "./EncounterHistory"; -import { HealthProfileSummary } from "./HealthProfileSummary"; -import { ImmunisationRecords } from "./ImmunisationRecords"; -import PatientNotes from "./Notes"; -import ShiftingHistory from "./ShiftingHistory"; +import EncounterHistory from "@/components/Patient/PatientDetailsTab//EncounterHistory"; +import { HealthProfileSummary } from "@/components/Patient/PatientDetailsTab//HealthProfileSummary"; +import { ImmunisationRecords } from "@/components/Patient/PatientDetailsTab//ImmunisationRecords"; +import PatientNotes from "@/components/Patient/PatientDetailsTab//Notes"; +import ShiftingHistory from "@/components/Patient/PatientDetailsTab//ShiftingHistory"; +import { Demography } from "@/components/Patient/PatientDetailsTab/Demography"; +import { PatientModel } from "@/components/Patient/models"; export interface PatientProps { facilityId: string; id: string; patientData: PatientModel; + refetch: () => void; } export const patientTabs = [ diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index 253bd823ac0..1c992445706 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -2,8 +2,22 @@ import { Link, navigate } from "raviger"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import Chip from "@/CAREUI/display/Chip"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Avatar } from "@/components/Common/Avatar"; +import ButtonV2 from "@/components/Common/ButtonV2"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; +import Loading from "@/components/Common/Loading"; +import Page from "@/components/Common/Page"; import UserAutocomplete from "@/components/Common/UserAutocompleteFormField"; +import { patientTabs } from "@/components/Patient/PatientDetailsTab"; +import { isPatientMandatoryDataFilled } from "@/components/Patient/Utils"; +import { + AssignedToObjectModel, + PatientModel, +} from "@/components/Patient/models"; +import { SkillModel, UserBareMinimum } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -13,16 +27,13 @@ import { OCCUPATION_TYPES, } from "@/common/constants"; +import { triggerGoal } from "@/Integrations/Plausible"; +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import * as Notification from "@/Utils/Notifications"; import dayjs from "@/Utils/dayjs"; import routes from "@/Utils/request/api"; - -import Chip from "../../CAREUI/display/Chip"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { triggerGoal } from "../../Integrations/Plausible"; -import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; -import * as Notification from "../../Utils/Notifications"; -import request from "../../Utils/request/request"; -import useTanStackQueryInstead from "../../Utils/request/useQuery"; +import request from "@/Utils/request/request"; +import useTanStackQueryInstead from "@/Utils/request/useQuery"; import { formatDateTime, formatName, @@ -31,15 +42,7 @@ import { isAntenatal, isPostPartum, relativeDate, -} from "../../Utils/utils"; -import { Avatar } from "../Common/Avatar"; -import ButtonV2 from "../Common/ButtonV2"; -import Loading from "../Common/Loading"; -import Page from "../Common/Page"; -import { SkillModel, UserBareMinimum } from "../Users/models"; -import { patientTabs } from "./PatientDetailsTab"; -import { isPatientMandatoryDataFilled } from "./Utils"; -import { AssignedToObjectModel, PatientModel } from "./models"; +} from "@/Utils/utils"; export const parseOccupation = (occupation: string | undefined) => { return OCCUPATION_TYPES.find((i) => i.value === occupation)?.text; @@ -447,6 +450,7 @@ export const PatientHome = (props: { facilityId={facilityId || ""} id={id} patientData={patientData} + refetch={refetch} /> )} From ba80ea9087a7f03c9dfb5f8a3a4767c6874bb3f1 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 17 Dec 2024 06:43:36 +0000 Subject: [PATCH 3/8] Adds `useMutation` compatible utility function and other abstractions (#9463) --- .cursorrules | 2 +- src/App.tsx | 8 ++- src/Utils/request/README.md | 82 +++++++++++++++++++++++- src/Utils/request/errorHandler.ts | 10 +-- src/Utils/request/mutate.ts | 26 ++++++++ src/Utils/request/query.ts | 29 ++++++--- src/Utils/request/queryError.ts | 24 ------- src/Utils/request/types.ts | 37 ++++++++++- src/Utils/request/utils.ts | 19 ++++-- src/components/Facility/FacilityHome.tsx | 12 +--- src/components/Users/UserAvatar.tsx | 8 +-- src/components/Users/UserProfile.tsx | 8 +-- 12 files changed, 194 insertions(+), 71 deletions(-) create mode 100644 src/Utils/request/mutate.ts delete mode 100644 src/Utils/request/queryError.ts diff --git a/.cursorrules b/.cursorrules index 94ebea1bd57..84ed409d44b 100644 --- a/.cursorrules +++ b/.cursorrules @@ -31,5 +31,5 @@ UI and Styling General Guidelines -- Care uses a custom useQuery hook to fetch data from the API. (Docs @ /Utils/request/useQuery) +- Care uses TanStack Query for data fetching from the API along with query and mutate utilities for the queryFn and mutationFn. (Docs @ /Utils/request/README.md) - APIs are defined in the api.tsx file. diff --git a/src/App.tsx b/src/App.tsx index b4d1a1570a9..1d8acbb8e59 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { + MutationCache, QueryCache, QueryClient, QueryClientProvider, @@ -16,7 +17,7 @@ import AuthUserProvider from "@/Providers/AuthUserProvider"; import HistoryAPIProvider from "@/Providers/HistoryAPIProvider"; import Routers from "@/Routers"; import { FeatureFlagsProvider } from "@/Utils/featureFlags"; -import { handleQueryError } from "@/Utils/request/errorHandler"; +import { handleHttpError } from "@/Utils/request/errorHandler"; import { PubSubProvider } from "./Utils/pubsubContext"; @@ -29,7 +30,10 @@ const queryClient = new QueryClient({ }, }, queryCache: new QueryCache({ - onError: handleQueryError, + onError: handleHttpError, + }), + mutationCache: new MutationCache({ + onError: handleHttpError, }), }); diff --git a/src/Utils/request/README.md b/src/Utils/request/README.md index 3c1279e1554..75dfab6c10f 100644 --- a/src/Utils/request/README.md +++ b/src/Utils/request/README.md @@ -67,10 +67,12 @@ function FacilityDetails({ id }: { id: string }) { - Integrates with our global error handling. ```typescript -interface QueryOptions { +interface APICallOptions { pathParams?: Record; // URL parameters - queryParams?: Record; // Query string parameters + queryParams?: QueryParams; // Query string parameters + body?: TBody; // Request body silent?: boolean; // Suppress error notifications + headers?: HeadersInit; // Additional headers } // Basic usage @@ -100,6 +102,82 @@ are automatically handled. Use the `silent: true` option to suppress error notifications for specific queries. +## Using Mutations with TanStack Query + +For data mutations, we provide a `mutate` utility that works seamlessly with TanStack Query's `useMutation` hook. + +```tsx +import { useMutation } from "@tanstack/react-query"; +import mutate from "@/Utils/request/mutate"; + +function CreatePrescription({ consultationId }: { consultationId: string }) { + const { mutate: createPrescription, isPending } = useMutation({ + mutationFn: mutate(MedicineRoutes.createPrescription, { + pathParams: { consultationId }, + }), + onSuccess: () => { + toast.success("Prescription created successfully"); + }, + }); + + return ( + + ); +} + +// With path parameters and complex payload +function UpdatePatient({ patientId }: { patientId: string }) { + const { mutate: updatePatient } = useMutation({ + mutationFn: mutate(PatientRoutes.update, { + pathParams: { id: patientId }, + silent: true // Optional: suppress error notifications + }) + }); + + const handleSubmit = (data: PatientData) => { + updatePatient(data); + }; + + return ; +} +``` + +### mutate + +`mutate` is our wrapper around the API call functionality that works with TanStack Query's `useMutation`. It: +- Handles request body serialization +- Sets appropriate headers +- Integrates with our global error handling +- Provides TypeScript type safety for your mutation payload + +```typescript +interface APICallOptions { + pathParams?: Record; // URL parameters + queryParams?: QueryParams; // Query string parameters + body?: TBody; // Request body + silent?: boolean; // Suppress error notifications + headers?: HeadersInit; // Additional headers +} + +// Basic usage +useMutation({ + mutationFn: mutate(routes.users.create) +}); + +// With parameters +useMutation({ + mutationFn: mutate(routes.users.update, { + pathParams: { id }, + silent: true // Optional: suppress error notifications + }) +}); +``` + ## Migration Guide & Reference ### Understanding the Transition diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 68d7e4600bb..c5609181f13 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -1,14 +1,14 @@ import { navigate } from "raviger"; import * as Notifications from "@/Utils/Notifications"; -import { QueryError } from "@/Utils/request/queryError"; +import { HTTPError } from "@/Utils/request/types"; -export function handleQueryError(error: Error) { +export function handleHttpError(error: Error) { if (error.name === "AbortError") { return; } - if (!(error instanceof QueryError)) { + if (!(error instanceof HTTPError)) { Notifications.Error({ msg: error.message || "Something went wrong!" }); return; } @@ -34,7 +34,7 @@ export function handleQueryError(error: Error) { }); } -function isSessionExpired(error: QueryError["cause"]) { +function isSessionExpired(error: HTTPError["cause"]) { return ( // If Authorization header is not valid error?.code === "token_not_valid" || @@ -49,6 +49,6 @@ function handleSessionExpired() { } } -function isBadRequest(error: QueryError) { +function isBadRequest(error: HTTPError) { return error.status === 400 || error.status === 406; } diff --git a/src/Utils/request/mutate.ts b/src/Utils/request/mutate.ts new file mode 100644 index 00000000000..2372920c162 --- /dev/null +++ b/src/Utils/request/mutate.ts @@ -0,0 +1,26 @@ +import { callApi } from "@/Utils/request/query"; +import { APICallOptions, Route } from "@/Utils/request/types"; + +/** + * Creates a TanStack Query compatible mutation function. + * + * Example: + * ```tsx + * const { mutate: createPrescription, isPending } = useMutation({ + * mutationFn: mutate(MedicineRoutes.createPrescription, { + * pathParams: { consultationId }, + * }), + * onSuccess: () => { + * toast.success(t("medication_request_prescribed")); + * }, + * }); + * ``` + */ +export default function mutate( + route: Route, + options?: APICallOptions, +) { + return (variables: TBody) => { + return callApi(route, { ...options, body: variables }); + }; +} diff --git a/src/Utils/request/query.ts b/src/Utils/request/query.ts index 53fe96878a2..dc79bd874ec 100644 --- a/src/Utils/request/query.ts +++ b/src/Utils/request/query.ts @@ -1,13 +1,12 @@ import careConfig from "@careConfig"; -import { QueryError } from "@/Utils/request/queryError"; import { getResponseBody } from "@/Utils/request/request"; -import { QueryOptions, Route } from "@/Utils/request/types"; +import { APICallOptions, HTTPError, Route } from "@/Utils/request/types"; import { makeHeaders, makeUrl } from "@/Utils/request/utils"; -async function queryRequest( +export async function callApi( { path, method, noAuth }: Route, - options?: QueryOptions, + options?: APICallOptions, ): Promise { const url = `${careConfig.apiUrl}${makeUrl(path, options?.queryParams, options?.pathParams)}`; @@ -32,7 +31,7 @@ async function queryRequest( const data = await getResponseBody(res); if (!res.ok) { - throw new QueryError({ + throw new HTTPError({ message: "Request Failed", status: res.status, silent: options?.silent ?? false, @@ -44,13 +43,27 @@ async function queryRequest( } /** - * Creates a TanStack Query compatible request function + * Creates a TanStack Query compatible query function. + * + * Example: + * ```tsx + * const { data, isLoading } = useQuery({ + * queryKey: ["prescription", consultationId], + * queryFn: query(MedicineRoutes.prescription, { + * pathParams: { consultationId }, + * queryParams: { + * limit: 10, + * offset: 0, + * }, + * }), + * }); + * ``` */ export default function query( route: Route, - options?: QueryOptions, + options?: APICallOptions, ) { return ({ signal }: { signal: AbortSignal }) => { - return queryRequest(route, { ...options, signal }); + return callApi(route, { ...options, signal }); }; } diff --git a/src/Utils/request/queryError.ts b/src/Utils/request/queryError.ts deleted file mode 100644 index cdfad312ef4..00000000000 --- a/src/Utils/request/queryError.ts +++ /dev/null @@ -1,24 +0,0 @@ -type QueryErrorCause = Record | undefined; - -export class QueryError extends Error { - status: number; - silent: boolean; - cause?: QueryErrorCause; - - constructor({ - message, - status, - silent, - cause, - }: { - message: string; - status: number; - silent: boolean; - cause?: Record; - }) { - super(message, { cause }); - this.status = status; - this.silent = silent; - this.cause = cause; - } -} diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 2b4be31d28d..a53a28fb0b0 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -35,15 +35,46 @@ export interface RequestOptions { silent?: boolean; } -export interface QueryOptions { - pathParams?: Record; - queryParams?: Record; +export interface APICallOptions { + pathParams?: Record; + queryParams?: QueryParams; body?: TBody; silent?: boolean; signal?: AbortSignal; headers?: HeadersInit; } +type HTTPErrorCause = Record | undefined; + +export class HTTPError extends Error { + status: number; + silent: boolean; + cause?: HTTPErrorCause; + + constructor({ + message, + status, + silent, + cause, + }: { + message: string; + status: number; + silent: boolean; + cause?: Record; + }) { + super(message, { cause }); + this.status = status; + this.silent = silent; + this.cause = cause; + } +} + +declare module "@tanstack/react-query" { + interface Register { + defaultError: HTTPError; + } +} + export interface PaginatedResponse { count: number; next: string | null; diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index 03427c17b44..26d69672f53 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -56,17 +56,24 @@ export function makeHeaders(noAuth: boolean, additionalHeaders?: HeadersInit) { headers.set("Content-Type", "application/json"); headers.append("Accept", "application/json"); - if (!noAuth) { - const token = localStorage.getItem(LocalStorageKeys.accessToken); - - if (token) { - headers.append("Authorization", `Bearer ${token}`); - } + const authorizationHeader = getAuthorizationHeader(); + if (authorizationHeader && !noAuth) { + headers.append("Authorization", authorizationHeader); } return headers; } +export function getAuthorizationHeader() { + const accessToken = localStorage.getItem(LocalStorageKeys.accessToken); + + if (accessToken) { + return `Bearer ${accessToken}`; + } + + return null; +} + export function mergeRequestOptions( options: RequestOptions, overrides: RequestOptions, diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 1808a1087ee..7881a43123d 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -28,11 +28,7 @@ import { FieldLabel } from "@/components/Form/FormFields/FormField"; import useAuthUser from "@/hooks/useAuthUser"; import useSlug from "@/hooks/useSlug"; -import { - FACILITY_FEATURE_TYPES, - LocalStorageKeys, - USER_TYPES, -} from "@/common/constants"; +import { FACILITY_FEATURE_TYPES, USER_TYPES } from "@/common/constants"; import { PLUGIN_Component } from "@/PluginEngine"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; @@ -42,6 +38,7 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getAuthorizationHeader } from "@/Utils/request/utils"; import { sleep } from "@/Utils/utils"; import { patientRegisterAuth } from "../Patient/PatientRegister"; @@ -121,10 +118,7 @@ export const FacilityHome = ({ facilityId }: Props) => { url, formData, "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, + { Authorization: getAuthorizationHeader() }, async (xhr: XMLHttpRequest) => { if (xhr.status === 200) { await sleep(1000); diff --git a/src/components/Users/UserAvatar.tsx b/src/components/Users/UserAvatar.tsx index db3620b34aa..77b353846bc 100644 --- a/src/components/Users/UserAvatar.tsx +++ b/src/components/Users/UserAvatar.tsx @@ -9,14 +9,13 @@ import Loading from "@/components/Common/Loading"; import useAuthUser from "@/hooks/useAuthUser"; -import { LocalStorageKeys } from "@/common/constants"; - import * as Notification from "@/Utils/Notifications"; import { showAvatarEdit } from "@/Utils/permissions"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getAuthorizationHeader } from "@/Utils/request/utils"; import { formatDisplayName, sleep } from "@/Utils/utils"; export default function UserAvatar({ username }: { username: string }) { @@ -47,10 +46,7 @@ export default function UserAvatar({ username }: { username: string }) { url, formData, "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, + { Authorization: getAuthorizationHeader() }, async (xhr: XMLHttpRequest) => { if (xhr.status === 200) { await sleep(1000); diff --git a/src/components/Users/UserProfile.tsx b/src/components/Users/UserProfile.tsx index 4dd98635510..786e569c4db 100644 --- a/src/components/Users/UserProfile.tsx +++ b/src/components/Users/UserProfile.tsx @@ -26,7 +26,7 @@ import { import useAuthUser, { useAuthContext } from "@/hooks/useAuthUser"; -import { GENDER_TYPES, LocalStorageKeys } from "@/common/constants"; +import { GENDER_TYPES } from "@/common/constants"; import { validateEmailAddress } from "@/common/validation"; import * as Notification from "@/Utils/Notifications"; @@ -35,6 +35,7 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getAuthorizationHeader } from "@/Utils/request/utils"; import { dateQueryString, formatDate, @@ -507,10 +508,7 @@ export default function UserProfile() { url, formData, "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, + { Authorization: getAuthorizationHeader() }, async (xhr: XMLHttpRequest) => { if (xhr.status === 200) { await sleep(1000); From 927c39949f7e4927a20ddb63bb8004d600d5d35b Mon Sep 17 00:00:00 2001 From: Mahendar Chikolla <119734520+Mahendar0701@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:40:17 +0530 Subject: [PATCH 4/8] Search UI for discharge patients (#9320) --- .../Facility/DischargedPatientsList.tsx | 202 ++++++++---------- 1 file changed, 95 insertions(+), 107 deletions(-) diff --git a/src/components/Facility/DischargedPatientsList.tsx b/src/components/Facility/DischargedPatientsList.tsx index 4fb8910f7a1..74bb6d95626 100644 --- a/src/components/Facility/DischargedPatientsList.tsx +++ b/src/components/Facility/DischargedPatientsList.tsx @@ -1,5 +1,5 @@ import { Link, navigate } from "raviger"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import CountBlock from "@/CAREUI/display/Count"; @@ -11,13 +11,11 @@ import PaginatedList from "@/CAREUI/misc/PaginatedList"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; +import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import SortDropdownMenu from "@/components/Common/SortDropdown"; import Tabs from "@/components/Common/Tabs"; import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; import { ICD11DiagnosisModel } from "@/components/Facility/models"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import SearchInput from "@/components/Form/SearchInput"; import { DIAGNOSES_FILTER_LABELS, DiagnosesFilterKey, @@ -52,16 +50,79 @@ const DischargedPatientsList = ({ pathParams: { id: facility_external_id }, }); - const { qParams, updateQuery, advancedFilter, FilterBadges, updatePage } = - useFilters({ - limit: 12, - cacheBlacklist: [ - "name", - "patient_no", - "phone_number", - "emergency_phone_number", - ], - }); + const { + qParams, + updateQuery, + advancedFilter, + FilterBadges, + updatePage, + clearSearch, + } = useFilters({ + limit: 12, + cacheBlacklist: [ + "name", + "patient_no", + "phone_number", + "emergency_phone_number", + ], + }); + + const searchOptions = [ + { + key: "name", + label: "Name", + type: "text" as const, + placeholder: "search_by_patient_name", + value: qParams.name || "", + shortcutKey: "n", + }, + { + key: "patient_no", + label: "IP/OP No", + type: "text" as const, + placeholder: "search_by_patient_no", + value: qParams.patient_no || "", + shortcutKey: "u", + }, + { + key: "phone_number", + label: "Phone Number", + type: "phone" as const, + placeholder: "Search_by_phone_number", + value: qParams.phone_number || "", + shortcutKey: "p", + }, + { + key: "emergency_contact_number", + label: "Emergency Contact Phone Number", + type: "phone" as const, + placeholder: "search_by_emergency_phone_number", + value: qParams.emergency_phone_number || "", + shortcutKey: "e", + }, + ]; + + const handleSearch = useCallback( + (key: string, value: string) => { + const isValidPhoneNumber = (val: string) => + val.length >= 13 || val === ""; + + const updatedQuery = { + phone_number: + key === "phone_number" && isValidPhoneNumber(value) + ? value + : undefined, + name: key === "name" ? value : undefined, + patient_no: key === "patient_no" ? value : undefined, + emergency_phone_number: + key === "emergency_contact_number" && isValidPhoneNumber(value) + ? value + : undefined, + }; + updateQuery(updatedQuery); + }, + [updateQuery], + ); useEffect(() => { if (!qParams.phone_number && phone_number.length >= 13) { @@ -200,56 +261,11 @@ const DischargedPatientsList = ({ }); }; - const queryField = (name: string, defaultValue?: T) => { - return { - name, - value: qParams[name] || defaultValue, - onChange: (e: FieldChangeEvent) => updateQuery({ [e.name]: e.value }), - className: "grow w-full mb-2", - }; - }; const [diagnoses, setDiagnoses] = useState([]); const [phone_number, setPhoneNumber] = useState(""); - const [phoneNumberError, setPhoneNumberError] = useState(""); const [emergency_phone_number, setEmergencyPhoneNumber] = useState(""); - const [emergencyPhoneNumberError, setEmergencyPhoneNumberError] = - useState(""); const [count, setCount] = useState(0); - - const setPhoneNum = (phone_number: string) => { - setPhoneNumber(phone_number); - if (phone_number.length >= 13) { - setPhoneNumberError(""); - updateQuery({ phone_number }); - return; - } - - if (phone_number === "+91" || phone_number === "") { - setPhoneNumberError(""); - qParams.phone_number && updateQuery({ phone_number: null }); - return; - } - - setPhoneNumberError("Enter a valid number"); - }; - - const setEmergencyPhoneNum = (emergency_phone_number: string) => { - setEmergencyPhoneNumber(emergency_phone_number); - if (emergency_phone_number.length >= 13) { - setEmergencyPhoneNumberError(""); - updateQuery({ emergency_phone_number }); - return; - } - - if (emergency_phone_number === "+91" || emergency_phone_number === "") { - setEmergencyPhoneNumberError(""); - qParams.emergency_phone_number && - updateQuery({ emergency_phone_number: null }); - return; - } - - setEmergencyPhoneNumberError("Enter a valid number"); - }; + const [isLoading, setIsLoading] = useState(false); return ( } > -
-
-
- -
-
-
-
-
- - -
-
- setPhoneNum(e.value)} - error={phoneNumberError} - types={["mobile", "landline"]} - /> - setEmergencyPhoneNum(e.value)} - error={emergencyPhoneNumberError} - types={["mobile", "landline"]} - /> -
-
+
+
+
+
setCount(query.data?.count || 0)} + queryCB={(query) => { + setCount(query.data?.count || 0); + setIsLoading(query.loading); + }} initialPage={qParams.page} onPageChange={updatePage} > From 9ac5225a7d1b1f202c49a63f169893dea875e153 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:40:38 +0530 Subject: [PATCH 5/8] Fixes UI Bug Issue in Symptom Editor (#9270) --- src/components/Common/DateInputV2.tsx | 1 + .../Form/FormFields/AutocompleteMultiselect.tsx | 8 +++++--- src/components/Form/FormFields/DateFormField.tsx | 5 ++++- src/components/Form/ModelCrudEditor.tsx | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/Common/DateInputV2.tsx b/src/components/Common/DateInputV2.tsx index 20027a004d2..8ca937aed72 100644 --- a/src/components/Common/DateInputV2.tsx +++ b/src/components/Common/DateInputV2.tsx @@ -331,6 +331,7 @@ const DateInputV2: React.FC = ({ data-scribe-ignore className={`cui-input-base cursor-pointer disabled:cursor-not-allowed ${className}`} placeholder={placeholder ?? t("select_date")} + title={placeholder} value={value ? dayjs(value).format(dateFormat) : ""} />
diff --git a/src/components/Form/FormFields/AutocompleteMultiselect.tsx b/src/components/Form/FormFields/AutocompleteMultiselect.tsx index 3c537dffa32..8dad2f3ff0d 100644 --- a/src/components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/components/Form/FormFields/AutocompleteMultiselect.tsx @@ -171,16 +171,18 @@ export const AutocompleteMutliSelect = ( {!props.disabled && ( -
+
val.option) ? "-top-5" : ""}`} + > {props.isLoading ? ( ) : ( )}
diff --git a/src/components/Form/FormFields/DateFormField.tsx b/src/components/Form/FormFields/DateFormField.tsx index e867c24dd51..a3d26fa5554 100644 --- a/src/components/Form/FormFields/DateFormField.tsx +++ b/src/components/Form/FormFields/DateFormField.tsx @@ -37,7 +37,10 @@ const DateFormField = (props: Props) => { return ( ( onClick={() => handleDelete(props.item.id)} className="w-full text-xl text-red-500 hover:text-red-700 disabled:grayscale md:w-auto" > - {" "} + {t("remove")} )} From 2e829994fda14310b1575849a4fb793322f529c4 Mon Sep 17 00:00:00 2001 From: Kamishetty Rishith <119791436+Rishith25@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:46:11 +0530 Subject: [PATCH 6/8] Occupancy bed tooltip fully visible in mobile view (#9235) --- src/components/Common/Sidebar/Sidebar.tsx | 38 +++++++----------- src/components/Facility/FacilityCard.tsx | 37 +++++++++++------ src/components/ui/tooltip.tsx | 48 ++++++++++++++++------- 3 files changed, 71 insertions(+), 52 deletions(-) diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 423d0d6f18b..e6d9edbc2dd 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -6,12 +6,7 @@ import { useTranslation } from "react-i18next"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; import SlideOver from "@/CAREUI/interactive/SlideOver"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; +import { TooltipComponent, TooltipProvider } from "@/components/ui/tooltip"; import { ShrinkedSidebarItem, @@ -243,24 +238,19 @@ const ToggleShrink = ({ shrinked, toggle }: ToggleShrinkProps) => { const { t } = useTranslation(); return ( - - - - - -

{shrinked ? t("expand_sidebar") : t("collapse_sidebar")}

-
-
+ + +
); }; diff --git a/src/components/Facility/FacilityCard.tsx b/src/components/Facility/FacilityCard.tsx index fbebe8a8d0a..2f6ecec204b 100644 --- a/src/components/Facility/FacilityCard.tsx +++ b/src/components/Facility/FacilityCard.tsx @@ -6,6 +6,8 @@ import { useTranslation } from "react-i18next"; import Chip from "@/CAREUI/display/Chip"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { TooltipComponent, TooltipProvider } from "@/components/ui/tooltip"; + import { Avatar } from "@/components/Common/Avatar"; import ButtonV2, { Cancel, Submit } from "@/components/Common/ButtonV2"; import DialogModal from "@/components/Common/Dialog"; @@ -98,19 +100,28 @@ export const FacilityCard = (props: { > {facility.name} -
0.85 ? "justify-center rounded-md border border-red-600 bg-red-500 p-1 font-bold text-white" : "text-secondary-700"}`} - > - - {t("live_patients_total_beds")} - {" "} - -
- {t("occupancy")}: {facility.patient_count} /{" "} - {facility.bed_count}{" "} -
-
+ + +
+ 0.85 + ? "justify-center rounded-md border border-red-600 bg-red-500 p-1 font-bold text-white" + : "text-secondary-700" + }`} + > + +
+ {t("occupancy")}: {facility.patient_count} /{" "} + {facility.bed_count} +
+
+
+
, +const TooltipContent = TooltipPrimitive.Content; + +const TooltipComponent = React.forwardRef< + React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - -)); -TooltipContent.displayName = TooltipPrimitive.Content.displayName; +>(({ children, content, sideOffset = 4, className }, ref) => { + const [open, setOpen] = React.useState(false); + return ( + + + setOpen(!open)}> + {children} + + + {content} + + + + ); +}); -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; +export { + TooltipComponent, + TooltipTrigger, + TooltipContent, + TooltipProvider, + Tooltip, +}; From 10bfd6cab8447380ece9ffbe7cf3227b1e1f8d21 Mon Sep 17 00:00:00 2001 From: Kamishetty Rishith <119791436+Rishith25@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:47:51 +0530 Subject: [PATCH 7/8] Disallowing out of date range (#9201) --- src/components/Common/DateInputV2.tsx | 274 +++++++++++++++++++------- 1 file changed, 208 insertions(+), 66 deletions(-) diff --git a/src/components/Common/DateInputV2.tsx b/src/components/Common/DateInputV2.tsx index 8ca937aed72..8debee7ac7b 100644 --- a/src/components/Common/DateInputV2.tsx +++ b/src/components/Common/DateInputV2.tsx @@ -89,15 +89,18 @@ const DateInputV2: React.FC = ({ ); break; case "month": - setDatePickerHeaderDate((prev) => - dayjs(prev).subtract(1, "year").toDate(), - ); + setDatePickerHeaderDate((prev) => { + const newDate = dayjs(prev).subtract(1, "year").toDate(); + if (min && newDate < min) { + return new Date(min.getFullYear(), min.getMonth(), 1); + } + return newDate; + }); break; case "year": - setDatePickerHeaderDate((prev) => - dayjs(prev).subtract(1, "year").toDate(), - ); - setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); + if (!min || year.getFullYear() - 10 >= min.getFullYear()) { + setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); + } break; } }; @@ -108,11 +111,18 @@ const DateInputV2: React.FC = ({ setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "month").toDate()); break; case "month": - setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); + setDatePickerHeaderDate((prev) => { + const newDate = dayjs(prev).add(1, "year").toDate(); + if (max && newDate > max) { + return new Date(max.getFullYear(), max.getMonth(), 1); + } + return newDate; + }); break; case "year": - setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); - setYear((prev) => dayjs(prev).add(10, "year").toDate()); + if (!max || year.getFullYear() + 10 <= max.getFullYear()) { + setYear((prev) => dayjs(prev).add(10, "year").toDate()); + } break; } }; @@ -209,6 +219,33 @@ const DateInputV2: React.FC = ({ return true; }; + const isMonthWithinConstraints = (month: number) => { + const year = datePickerHeaderDate.getFullYear(); + + if (min && year < min.getFullYear()) return false; + if (max && year > max.getFullYear()) return false; + + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + if (min && lastDay < min) return false; + if (max && firstDay > max) return false; + + return true; + }; + + const isYearWithinConstraints = (year: number) => { + if (min && year < min.getFullYear()) return false; + if (max && year > max.getFullYear()) return false; + + const yearStart = new Date(year, 0, 1); + const yearEnd = new Date(year, 11, 31); + + if (min && yearEnd < min) return false; + if (max && yearStart > max) return false; + + return true; + }; + const isSelectedMonth = (month: number) => month === datePickerHeaderDate.getMonth(); @@ -216,25 +253,48 @@ const DateInputV2: React.FC = ({ year === datePickerHeaderDate.getFullYear(); const setMonthValue = (month: number) => () => { - setDatePickerHeaderDate( - new Date( + if (isMonthWithinConstraints(month)) { + const lastDayOfMonth = new Date( datePickerHeaderDate.getFullYear(), - month, - datePickerHeaderDate.getDate(), - ), - ); - setType("date"); + month + 1, + 0, + ).getDate(); + const newDate = Math.min(datePickerHeaderDate.getDate(), lastDayOfMonth); + setDatePickerHeaderDate( + new Date(datePickerHeaderDate.getFullYear(), month, newDate), + ); + setType("date"); + } else { + Notification.Error({ + msg: outOfLimitsErrorMessage ?? "Cannot select month out of range", + }); + } }; - + //min and max setting for year const setYearValue = (year: number) => () => { - setDatePickerHeaderDate( - new Date( + if (isYearWithinConstraints(year)) { + const newDate = new Date( year, datePickerHeaderDate.getMonth(), datePickerHeaderDate.getDate(), - ), - ); - setType("date"); + ); + if (min && year === min.getFullYear() && newDate < min) { + setDatePickerHeaderDate( + new Date(min.getFullYear(), min.getMonth(), min.getDate()), + ); + } else if (max && year === max.getFullYear() && newDate > max) { + setDatePickerHeaderDate( + new Date(max.getFullYear(), max.getMonth(), max.getDate()), + ); + } else { + setDatePickerHeaderDate(newDate); + } + setType("date"); + } else { + Notification.Error({ + msg: outOfLimitsErrorMessage ?? "Cannot select year out of range", + }); + } }; useEffect(() => { @@ -372,23 +432,62 @@ const DateInputV2: React.FC = ({
- + {type === "date" && ( + + )} + {type === "month" && ( + + )} + + {type === "year" && ( + + )}
{type === "date" && ( @@ -412,23 +511,62 @@ const DateInputV2: React.FC = ({

- + {type === "date" && ( + + )} + {type === "month" && ( + + )} + + {type === "year" && ( + + )}
{type === "date" && ( @@ -511,10 +649,12 @@ const DateInputV2: React.FC = ({ key={i} id={`month-${i}`} className={classNames( - "w-1/4 cursor-pointer rounded-lg px-2 py-4 text-center text-sm font-semibold", - value && isSelectedMonth(i) - ? "bg-primary-500 text-white" - : "text-secondary-700 hover:bg-secondary-300", + "w-1/4 rounded-lg px-2 py-4 text-center text-sm font-semibold", + isSelectedMonth(i) + ? "bg-primary-500 text-white cursor-pointer" + : isMonthWithinConstraints(i) + ? "text-secondary-700 hover:bg-secondary-300 cursor-pointer" + : "!text-secondary-400 !cursor-not-allowed", )} onClick={setMonthValue(i)} > @@ -534,16 +674,18 @@ const DateInputV2: React.FC = ({ {Array(12) .fill(null) .map((_, i) => { - const y = year.getFullYear() - 11 + i; + const y = year.getFullYear() - 10 + i; return (
From f56e6f5eace6562a7252ffacc2790a02c620eca2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:45:57 +0530 Subject: [PATCH 8/8] Bump @sentry/browser from 8.42.0 to 8.45.1 (#9473) Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 8.42.0 to 8.45.1. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/8.45.1/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/8.42.0...8.45.1) --- updated-dependencies: - dependency-name: "@sentry/browser" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 60 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94a84d3d179..c6076acd00a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.4", - "@sentry/browser": "^8.42.0", + "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8", @@ -4796,50 +4796,50 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz", - "integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.45.1.tgz", + "integrity": "sha512-sZwtP3zAzDsjUS7WkMW5VGbvSl7hGKTMc8gAJbpEsrybMxllIP13zzMRwpeFF11RnnvbrZ/FtAeX58Mvj0jahA==", "license": "MIT", "dependencies": { - "@sentry/core": "8.42.0" + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz", - "integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.45.1.tgz", + "integrity": "sha512-zCKptzki4SLnG+s8je8dgnppOKFjiiO4GVBc4fh7uL8zjNPBnxW8wK4SrPfAEKVYaHUzkKc5vixwUqcpmfLLGw==", "license": "MIT", "dependencies": { - "@sentry/core": "8.42.0" + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz", - "integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.45.1.tgz", + "integrity": "sha512-cOA9CodNSR9+hmICDaGIDUvWiwxQxeMHk/esbjB8uAW8HG4CYTG3CTYTZmlmou7DuysfMd4JNuFmDFBj+YU5/A==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/browser-utils": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz", - "integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.45.1.tgz", + "integrity": "sha512-qiPg6XwOwkiMMe/8Qf3EhXCqkSlSnWLlorYngIbdkV2klbWjd7vKnqkFJF4PnaS0g7kkZr7nh+MdzpyLyuj2Mw==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/replay": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" @@ -4898,25 +4898,25 @@ } }, "node_modules/@sentry/browser": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz", - "integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.45.1.tgz", + "integrity": "sha512-/KvYhQSRg8m9kotG8h9FrfXCWRlebrvdfXKjj1oE9SyZ2LmR8Ze9AcEw1qzsBsa1F1D/a5FQbUJahSoLBkaQPA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.42.0", - "@sentry-internal/feedback": "8.42.0", - "@sentry-internal/replay": "8.42.0", - "@sentry-internal/replay-canvas": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/browser-utils": "8.45.1", + "@sentry-internal/feedback": "8.45.1", + "@sentry-internal/replay": "8.45.1", + "@sentry-internal/replay-canvas": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/core": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz", - "integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.45.1.tgz", + "integrity": "sha512-1fGmkr0paZshh38mD29c4CfkRkgFoYDaAGyDLoGYfTbEph/lU8RHB2HWzN93McqNdMEhl1DRRyqIasUZoPlqSA==", "license": "MIT", "engines": { "node": ">=14.18" diff --git a/package.json b/package.json index e5eaf3342c9..00ec917f6fb 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.4", - "@sentry/browser": "^8.42.0", + "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8",