diff --git a/.example.env b/.example.env index dd4a8865878..127db15e710 100644 --- a/.example.env +++ b/.example.env @@ -69,7 +69,6 @@ REACT_STILL_WATCHING_PROMPT_DURATION= # Feature flags REACT_ENABLE_HCX=true REACT_ENABLE_ABDM=true -REACT_ENABLE_SCRIBE=true REACT_WARTIME_SHIFTING=true # JWT token refresh interval (in milliseconds) (default: 5 minutes) diff --git a/README.md b/README.md index fd5328150f2..73ce06815d1 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ Authenticate to staging API with any of the following credentials - Once the code review is done, the PR will be marked with a "Needs Testing" label where it'll be queued for QA testing. - Once tested, the PR would be marked with a "Tested" label and would be queued for merge. +### Translations + +All strings must be encased in i18n translations. New translation strings must be specified in `src`->`Locale`->`en`. Do not add translations for languages other than english through pull requests. Other language translations can be contributed through [Crowdin](https://crowdin.com/project/ohccarefe) + ### Testing To ensure the quality of our pull requests, we use a variety of tools: diff --git a/care.config.ts b/care.config.ts index 4341a03dd48..e3effeca1b0 100644 --- a/care.config.ts +++ b/care.config.ts @@ -103,10 +103,6 @@ const careConfig = { abdm: { enabled: (env.REACT_ENABLE_ABDM ?? "true") === "true", }, - - scribe: { - enabled: env.REACT_ENABLE_SCRIBE === "true", - }, } as const; export default careConfig; diff --git a/src/App.tsx b/src/App.tsx index 2e7f185f80b..6c6d5255b4d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ import { Suspense } from "react"; import Routers from "./Routers"; import ThemedFavicon from "./CAREUI/misc/ThemedFavicon"; -import Intergrations from "./Integrations"; +import Integrations from "./Integrations"; import Loading from "./Components/Common/Loading"; import HistoryAPIProvider from "./Providers/HistoryAPIProvider"; import AuthUserProvider from "./Providers/AuthUserProvider"; +import { FeatureFlagsProvider } from "./Utils/featureFlags"; const App = () => { return ( @@ -12,12 +13,14 @@ const App = () => { }> - + + + {/* Integrations */} - - + + ); diff --git a/src/Components/Facility/AddBedForm.tsx b/src/Components/Facility/AddBedForm.tsx index 0a612fa776b..047549bd652 100644 --- a/src/Components/Facility/AddBedForm.tsx +++ b/src/Components/Facility/AddBedForm.tsx @@ -127,7 +127,8 @@ export const AddBedForm = ({ facilityId, locationId, bedId }: Props) => { const { res } = await request(routes.createFacilityBed, { body: { ...data, facility: facilityId, location: locationId }, }); - res?.ok && onSuccess("Bed(s) created successfully"); + res?.ok && + onSuccess(t("bed_created_notification", { count: numberOfBeds })); } }; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 97d81674658..56c70f2ac94 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -7,6 +7,7 @@ import { PATIENT_NOTES_THREADS, UserRole, } from "../../Common/constants"; +import { FeatureFlag } from "../../Utils/featureFlags"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { AssignedToObjectModel, @@ -80,6 +81,7 @@ export interface FacilityModel { local_body?: number; ward?: number; pincode?: string; + facility_flags?: FeatureFlag[]; latitude?: string; longitude?: string; kasp_empanelled?: boolean; diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index c0cd7fe803c..ef1281fb62b 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -542,6 +542,7 @@ export const DailyRounds = (props: any) => { >
{ setDiagnosisSuggestions([]); diff --git a/src/Components/Scribe/Scribe.tsx b/src/Components/Scribe/Scribe.tsx index d952ff7b1b2..384bf519a04 100644 --- a/src/Components/Scribe/Scribe.tsx +++ b/src/Components/Scribe/Scribe.tsx @@ -7,8 +7,8 @@ import * as Notify from "../../Utils/Notifications"; import request from "../../Utils/request/request"; import { UserModel } from "../Users/models"; import useSegmentedRecording from "../../Utils/useSegmentedRecorder"; -import careConfig from "@careConfig"; import uploadFile from "../../Utils/request/uploadFile"; +import { useFeatureFlags } from "../../Utils/featureFlags"; interface FieldOption { id: string | number; @@ -52,6 +52,7 @@ export type ScribeModel = { }; interface ScribeProps { + facilityId: string; form: ScribeForm; existingData?: { [key: string]: any }; onFormUpdate: (fields: any) => void; @@ -62,7 +63,11 @@ const SCRIBE_FILE_TYPES = { SCRIBE: 1, }; -export const Scribe: React.FC = ({ form, onFormUpdate }) => { +export const Scribe: React.FC = ({ + form, + onFormUpdate, + facilityId, +}) => { const [open, setOpen] = useState(false); const [_progress, setProgress] = useState(0); const [stage, setStage] = useState("start"); @@ -80,6 +85,8 @@ export const Scribe: React.FC = ({ form, onFormUpdate }) => { const stageRef = useRef(stage); const [fields, setFields] = useState([]); + const featureFlags = useFeatureFlags(facilityId); + useEffect(() => { const loadFields = async () => { const fields = await form.fields(); @@ -544,7 +551,7 @@ export const Scribe: React.FC = ({ form, onFormUpdate }) => { } } - if (!careConfig.scribe.enabled) return null; + if (!featureFlags.includes("SCRIBE_ENABLED")) return null; return ( diff --git a/src/Components/Users/models.tsx b/src/Components/Users/models.tsx index 1bbe494b9ed..790826022b5 100644 --- a/src/Components/Users/models.tsx +++ b/src/Components/Users/models.tsx @@ -1,4 +1,5 @@ import { GENDER_TYPES, UserRole } from "../../Common/constants"; +import { FeatureFlag } from "../../Utils/featureFlags"; import { DistrictModel, LocalBodyModel, StateModel } from "../Facility/models"; interface HomeFacilityObjectModel { @@ -44,6 +45,7 @@ export type UserModel = UserBareMinimum & { doctor_experience_commenced_on?: string; doctor_medical_council_registration?: string; weekly_working_hours?: string | null; + user_flags?: FeatureFlag[]; }; export type UserBaseModel = { diff --git a/src/Integrations/index.tsx b/src/Integrations/index.tsx index aeb0399a452..9b2b1e156fd 100644 --- a/src/Integrations/index.tsx +++ b/src/Integrations/index.tsx @@ -1,6 +1,6 @@ import Sentry from "./Sentry"; import Plausible from "./Plausible"; -const Intergrations = { Sentry, Plausible }; +const Integrations = { Sentry, Plausible }; -export default Intergrations; +export default Integrations; diff --git a/src/Locale/TRANSLATION_CONTRIBUTION.md b/src/Locale/TRANSLATION_CONTRIBUTION.md index c262215c643..f3150ae8fc0 100644 --- a/src/Locale/TRANSLATION_CONTRIBUTION.md +++ b/src/Locale/TRANSLATION_CONTRIBUTION.md @@ -1,29 +1,13 @@ -# Contributing Translation +# Contributing Translations -### For adding a new language - -
+## Adding a new language - Open the Terminal and `cd` to `care_fe/src/Locale` - Run the command `node update_locale.js ` Eg: `node update_locale.js ml` for Malayalam -- The command will create a directory with default locale files and you can start translating them. +- The command will create a directory with default locale files. - After it's done, add the new language to `care_fe/src/Locale/config.ts` file. -### For improving the existing language - -
- -- Open the Terminal and `cd` to `care_fe/src/Locale` -- Run the command `node update_locale.js ` - Eg: `node update_locale.js ml` for Malayalam -- The command will update the new keys which are yet to be translated. -- You can now start translating or improving it. - ## Note -⚠ - If you are adding a new word, then please add it to the Default Locale (EN) first and then proceed with your language. - -⚠ - After translating, have a look at its appearance. It may be overflowing or cause some UI breaks. Try to adjust the words such that it fits the UI. - ⚠ - Try to separate the translation files for each module like `Facility`, `Patient` and more. Don't dump all the keys in one JSON file. diff --git a/src/Locale/en/Bed.json b/src/Locale/en/Bed.json index 269658be774..327a4533353 100644 --- a/src/Locale/en/Bed.json +++ b/src/Locale/en/Bed.json @@ -9,5 +9,7 @@ "bed_type": "Bed Type", "make_multiple_beds_label": "Do you want to make multiple beds?", "number_of_beds": "Number of beds", - "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100" + "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", + "bed_created_notification_one": "{{count}} Bed created successfully", + "bed_created_notification_other": "{{count}} Beds created successfully" } diff --git a/src/Utils/featureFlags.tsx b/src/Utils/featureFlags.tsx new file mode 100644 index 00000000000..739d49e821a --- /dev/null +++ b/src/Utils/featureFlags.tsx @@ -0,0 +1,78 @@ +import { createContext, useContext, useState, useEffect } from "react"; +import useQuery from "./request/useQuery"; +import routes from "../Redux/api"; +import useAuthUser from "../Common/hooks/useAuthUser"; +import { FacilityModel } from "../Components/Facility/models"; + +export type FeatureFlag = "SCRIBE_ENABLED"; // "HCX_ENABLED" | "ABDM_ENABLED" | + +export interface FeatureFlagsResponse { + user_flags: FeatureFlag[]; + facility_flags: { + facility: string; + features: FeatureFlag[]; + }[]; +} + +const defaultFlags: FeatureFlag[] = []; + +const FeatureFlagsContext = createContext({ + user_flags: defaultFlags, + facility_flags: [], +}); + +export const FeatureFlagsProvider = (props: { children: React.ReactNode }) => { + const [featureFlags, setFeatureFlags] = useState({ + user_flags: defaultFlags, + facility_flags: [], + }); + + const user = useAuthUser(); + + useEffect(() => { + if (user.user_flags) { + setFeatureFlags((ff) => ({ + ...ff, + user_flags: [...defaultFlags, ...(user.user_flags || [])], + })); + } + }, [user]); + + return ( + + {props.children} + + ); +}; + +export const useFeatureFlags = (facility?: FacilityModel | string) => { + const [facilityObject, setFacilityObject] = useState< + FacilityModel | undefined + >(typeof facility === "string" ? undefined : facility); + + const context = useContext(FeatureFlagsContext); + if (context === undefined) { + throw new Error( + "useFeatureFlags must be used within a FeatureFlagsProvider", + ); + } + + const facilityQuery = useQuery(routes.getPermittedFacility, { + pathParams: { + id: typeof facility === "string" ? facility : "", + }, + prefetch: false, + silent: true, + onResponse: (res) => { + setFacilityObject(res.data); + }, + }); + + const facilityFlags = facilityObject?.facility_flags || []; + + useEffect(() => { + facilityQuery.refetch(); + }, [facility]); + + return [...context.user_flags, ...facilityFlags]; +};