From 1637e005acb587f8d14ed8161beb6ebac2d082fa Mon Sep 17 00:00:00 2001 From: Gigin George Date: Sun, 10 Nov 2024 10:50:40 +0000 Subject: [PATCH 01/23] WIP Microfrontends; Add few shadcn components --- .cursorrules | 40 ++++- package-lock.json | 157 ++++++++++++++--- package.json | 5 +- src/Locale/en.json | 3 + src/Redux/api.tsx | 30 ++++ src/Routers/AppRouter.tsx | 18 +- src/Utils/AuthorizeFor.tsx | 4 +- src/components/Common/Sidebar/Sidebar.tsx | 1 + .../{404.tsx => DefaultErrorPage.tsx} | 33 +++- .../Facility/ConsultationDetails/index.tsx | 4 +- src/components/Patient/PatientRegister.tsx | 4 +- src/components/ui/alert-dialog.tsx | 139 +++++++++++++++ src/components/ui/card.tsx | 83 +++++++++ src/components/ui/input.tsx | 25 +++ src/components/ui/label.tsx | 24 +++ src/components/ui/table.tsx | 120 +++++++++++++ src/components/ui/textarea.tsx | 24 +++ src/pages/Apps/PlugConfigEdit.tsx | 158 ++++++++++++++++++ src/pages/Apps/PlugConfigList.tsx | 59 +++++++ src/types/plugConfig.ts | 4 + vite.config.mts | 8 + 21 files changed, 898 insertions(+), 45 deletions(-) rename src/components/ErrorPages/{404.tsx => DefaultErrorPage.tsx} (51%) create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/pages/Apps/PlugConfigEdit.tsx create mode 100644 src/pages/Apps/PlugConfigList.tsx create mode 100644 src/types/plugConfig.ts diff --git a/.cursorrules b/.cursorrules index 2eed3afe459..909a90e941d 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,18 +1,48 @@ Care is a React Typescript Project, built with Vite and styled with TailwindCSS. -Care uses a Plugin Architecture. Apps are installed in /apps. +Pages are defined in /src/pages. -Care uses a custom useQuery hook to fetch data from the API. APIs are defined in the api.tsx file +The React Components for the pages are defined in /src/components. Care primarily uses shadcn/ui components. -Here's an example of how to use the useQuery hook to fetch data from the API: +Routing for the React Pages are defined in /src/Routers/AppRouter.tsx using `raviger`. The AppRouter has a Routes object that maps paths to React Components. + +For Icons, Care uses an implementation of Unicons which can be used like this: + +``` +import CareIcon from "@/CAREUI/icons/CareIcon"; + + +``` + +The main routes are accessible from the BaseNavItems defined within the StatelessSidebar component in /src/components/Common/Sidebar/Sidebar.tsx. + +Care uses a custom useQuery hook to fetch data from the API. API routes are defined in the api.tsx file like: ``` -useQuery from "@/common/hooks/useQuery"; +routes = { + createScribe: { + path: "/api/care_scribe/scribe/", + method: "POST", + TReq: Type(), + TRes: Type(), + }, + ...otherRoutes +} +``` + +Here's an example of how to use the useQuery hook to fetch data from the API + +``` +import useQuery from "@/Utils/request/useQuery"; const { data, loading, error } = useQuery(routes.getFacilityUsers, { facility_id: "1", }); +``` + +Here's an example of how to make a request to the API -request from "@/common/utils/request"; +``` +import request from "@/Utils/request/request"; const { res } = await request(routes.partialUpdateAsset, { pathParams: { external_id: assetId }, body: data, diff --git a/package-lock.json b/package-lock.json index a4276cb3d35..c76af19ed00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -61,6 +63,7 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.6", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", @@ -480,11 +483,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", - "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1889,8 +1891,7 @@ "node_modules/@bufbuild/protobuf": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", - "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", - "license": "(Apache-2.0 AND BSD-3-Clause)" + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2796,7 +2797,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.11.10.tgz", "integrity": "sha512-PvFlKq1W64b9GfFjG7L4/o7ulAl5yFFpDTvG+JHQiXkaPaecMPt/qPbs6zdvUlC7om1TGMuW/pIN7o585Xz9Fg==", - "license": "Apache-2.0", "dependencies": { "@floating-ui/dom": "1.6.11", "loglevel": "1.9.1", @@ -2814,7 +2814,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.6.7.tgz", "integrity": "sha512-z8dgrBrRXIe7oagwFyjehdwL/4zpySJyPdAjeMDXZVbTXYNAugb3a88Ws9yQz4PZFECLkIPXJCN3C3YR+bgh5Q==", - "license": "Apache-2.0", "dependencies": { "@livekit/components-core": "0.11.10", "clsx": "2.1.1", @@ -2840,7 +2839,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.4.tgz", "integrity": "sha512-QCupn7tQ/dy/WZclrfsgtDe8peiGYS6Ied1IGkKOysaXo04l90t62SIUTKyxgd0dNDhUDC0p34qCggGZs/44lQ==", - "license": "Apache-2.0", "engines": { "node": ">=18" } @@ -2848,14 +2846,12 @@ "node_modules/@livekit/mutex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.0.0.tgz", - "integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==", - "license": "Apache-2.0" + "integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==" }, "node_modules/@livekit/protocol": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.24.0.tgz", "integrity": "sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==", - "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^1.10.0" } @@ -3268,6 +3264,41 @@ "node": "^16.13.0 || >=18.0.0" } }, + "node_modules/@originjs/vite-plugin-federation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-federation/-/vite-plugin-federation-1.3.6.tgz", + "integrity": "sha512-tHLMjdMJFPFMSJrUuJJiv8l7OFRvM19E9O1B9dhbk+04i3RnYwE9A6oNtSUM1dnvkalzCLwZIuMpti28/tnh8g==", + "dev": true, + "dependencies": { + "estree-walker": "^3.0.2", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0", + "pnpm": ">=7.0.1" + } + }, + "node_modules/@originjs/vite-plugin-federation/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@originjs/vite-plugin-federation/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3312,6 +3343,33 @@ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.2.tgz", + "integrity": "sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -3406,6 +3464,41 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -3544,6 +3637,28 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", @@ -3752,7 +3867,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -11704,10 +11818,9 @@ } }, "node_modules/livekit-client": { - "version": "2.5.10", - "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.5.10.tgz", - "integrity": "sha512-H7EeIb19LAH8ejlvhh0JWtWkvXDan6Yf3bpFGlDMb54uPmyRgBY+McfgQsFgJCB9WJL0X+GYUoV1Cmnn8iAoIQ==", - "license": "Apache-2.0", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.6.0.tgz", + "integrity": "sha512-hpxNBtyWIFCefoHjHoSjqPCw3m7AfSJVcVZw6rMsqds4u+dSpWLfYkglWP8JuPGUIssyOsZm/+bV3gBWfuOGGQ==", "dependencies": { "@livekit/mutex": "1.0.0", "@livekit/protocol": "1.24.0", @@ -11723,8 +11836,7 @@ "node_modules/livekit-client/node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/load-plugin": { "version": "6.0.3", @@ -11935,7 +12047,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", - "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -17203,7 +17314,6 @@ "version": "2.14.2", "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz", "integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==", - "license": "MIT", "bin": { "sdp-verify": "checker.js" } @@ -18536,8 +18646,7 @@ "node_modules/ts-debounce": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz", - "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==", - "license": "MIT" + "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==" }, "node_modules/ts-interface-checker": { "version": "0.1.13", @@ -18723,7 +18832,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", - "license": "MIT", "optionalDependencies": { "rxjs": "*" } @@ -19309,7 +19417,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==", - "license": "MIT", "dependencies": { "lodash.debounce": "^4.0.8" }, diff --git a/package.json b/package.json index e779081dc4b..86e87b05f61 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,10 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -100,6 +102,7 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.6", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", @@ -168,4 +171,4 @@ "node": ">=20.12.0" }, "packageManager": "npm@10.5.0" -} \ No newline at end of file +} diff --git a/src/Locale/en.json b/src/Locale/en.json index 784dd9d3905..7a7f17de979 100644 --- a/src/Locale/en.json +++ b/src/Locale/en.json @@ -294,6 +294,7 @@ "any_id": "Enter any ID linked with your ABHA number", "any_id_description": "Currently we support: Aadhaar Number / Mobile Number", "any_other_comments": "Any other comments", + "app_settings": "App Settings", "apply": "Apply", "approved_by_district_covid_control_room": "Approved by District COVID Control Room", "approving_facility": "Name of Approving Facility", @@ -484,6 +485,7 @@ "continue_watching": "Continue watching", "contribute_github": "Contribute on Github", "copied_to_clipboard": "Copied to clipboard", + "could_not_load_page": "We are facing some difficulties showing the Page you were looking for. Our Engineers have been notified and we'll make sure that this is resolved on the fly!", "countries_travelled": "Countries travelled", "covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)", "covid_19_death_reporting_form_1": "Covid-19 Death Reporting : Form 1", @@ -915,6 +917,7 @@ "otp_verification_success": "OTP has been verified successfully.", "out_of_range_error": "Value must be between {{ start }} and {{ end }}.", "oxygen_information": "Oxygen Information", + "page_load_error": "Couldn't Load the Page", "page_not_found": "Page Not Found", "pain": "Pain", "pain_chart_description": "Mark region and intensity of pain", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 22f0285d22d..1a76af39f9a 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -105,6 +105,7 @@ import { } from "@/components/ABDM/types/health-facility"; import { PMJAYPackageItem } from "@/components/Common/PMJAYProcedurePackageAutocomplete"; import { InsurerOptionModel } from "@/components/HCX/InsurerAutocomplete"; +import { PlugConfig } from "@/types/plugConfig"; /** * A fake function that returns an empty object casted to type T @@ -1807,6 +1808,35 @@ const routes = { }, }, }, + plugConfig: { + listPlugConfigs: { + path: "/api/v1/plug_config/", + method: "GET", + TRes: Type<{ configs: PlugConfig[] }>(), + }, + getPlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "GET", + TRes: Type(), + }, + createPlugConfig: { + path: "/api/v1/plug_config/", + method: "POST", + TReq: Type(), + TRes: Type(), + }, + updatePlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "PATCH", + TReq: Type(), + TRes: Type(), + }, + deletePlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "DELETE", + TRes: Type>(), + }, + }, } as const; export default routes; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 5a7b2cbb312..cd7edcea6a4 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; import ShowPushNotification from "@/components/Notifications/ShowPushNotification"; import { NoticeBoard } from "@/components/Notifications/NoticeBoard"; -import Error404 from "@/components/ErrorPages/404"; +import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; import { DesktopSidebar, MobileSidebar, @@ -27,6 +27,9 @@ import ResourceRoutes from "./routes/ResourceRoutes"; import { usePluginRoutes } from "@/common/hooks/useCareApps"; import careConfig from "@careConfig"; import IconIndex from "../CAREUI/icons/Index"; +import { PlugConfigList } from "@/pages/Apps/PlugConfigList"; +import { PlugConfigEdit } from "@/pages/Apps/PlugConfigEdit"; +import ErrorBoundary from "@/components/Common/ErrorBoundary"; export type RouteParams = T extends `${string}:${infer Param}/${infer Rest}` @@ -66,11 +69,14 @@ const Routes: AppRoutes = { ), "/session-expired": () => , - "/not-found": () => , + "/not-found": () => , "/icons": () => , // Only include the icon route in development environment ...(import.meta.env.PROD ? { "/icons": () => } : {}), + + "/apps": () => , + "/apps/plug-configs/:slug": ({ slug }) => , }; export default function AppRouter() { @@ -90,7 +96,7 @@ export default function AppRouter() { ...pluginRoutes, }; - const pages = useRoutes(routes) || ; + const pages = useRoutes(routes) || ; const path = usePath(); const [sidebarOpen, setSidebarOpen] = useState(false); @@ -170,7 +176,11 @@ export default function AppRouter() { className="flex-1 overflow-y-scroll bg-gray-100 pb-4 focus:outline-none md:py-0" >
- {pages} + } + > + {pages} +
diff --git a/src/Utils/AuthorizeFor.tsx b/src/Utils/AuthorizeFor.tsx index 6e1e048ee4e..b3d63e08bff 100644 --- a/src/Utils/AuthorizeFor.tsx +++ b/src/Utils/AuthorizeFor.tsx @@ -1,7 +1,7 @@ import { UserRole } from "@/common/constants"; import React from "react"; import useAuthUser from "@/common/hooks/useAuthUser"; -import Error404 from "@/components/ErrorPages/404"; +import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; export type AuthorizedForCB = (userType: UserRole) => boolean; @@ -44,6 +44,6 @@ export const AuthorizeUserRoute: React.FC = ({ if (userTypes.includes(authUser.user_type)) { return <>{children}; } else { - return ; + return ; } }; diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 2b117562fc9..16b6df97585 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -56,6 +56,7 @@ const StatelessSidebar = ({ { text: t("resource"), to: "/resource", icon: "l-heart-medical" }, { text: t("users"), to: "/users", icon: "l-users-alt" }, { text: t("notice_board"), to: "/notice_board", icon: "l-meeting-board" }, + { text: t("app_settings"), to: "/apps", icon: "l-setting" }, ]; const PluginNavItems = useCareAppNavItems(); diff --git a/src/components/ErrorPages/404.tsx b/src/components/ErrorPages/DefaultErrorPage.tsx similarity index 51% rename from src/components/ErrorPages/404.tsx rename to src/components/ErrorPages/DefaultErrorPage.tsx index ebb6b5cb789..7b9f7861cad 100644 --- a/src/components/ErrorPages/404.tsx +++ b/src/components/ErrorPages/DefaultErrorPage.tsx @@ -3,18 +3,43 @@ import * as Notification from "../../Utils/Notifications"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -export default function Error404() { +type ErrorType = "PAGE_NOT_FOUND" | "PAGE_LOAD_ERROR"; + +interface ErrorPageProps { + forError?: ErrorType; +} + +export default function ErrorPage({ + forError = "PAGE_NOT_FOUND", +}: ErrorPageProps) { const { t } = useTranslation(); + useEffect(() => { Notification.closeAllNotifications(); }, []); + + const errorContent = { + PAGE_NOT_FOUND: { + image: "/images/404.svg", + title: t("page_not_found"), + message: t("404_message"), + }, + PAGE_LOAD_ERROR: { + image: "/images/404.svg", + title: t("page_load_error"), + message: t("could_not_load_page"), + }, + }; + + const { image, title, message } = errorContent[forError]; + return (
- {t("error_404")} -

{t("page_not_found")}

+ {title} +

{title}

- {t("404_message")} + {message}

{ }; if (!tab) { - return ; + return ; } const SelectedTab = TABS[tab]; diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 6aab7799772..96286f19916 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -44,7 +44,7 @@ import ConfirmDialog from "@/components/Common/ConfirmDialog"; import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "@/components/Common/Dialog"; import DuplicatePatientDialog from "../Facility/DuplicatePatientDialog"; -import Error404 from "../ErrorPages/404"; +import ErrorPage from "../ErrorPages/DefaultErrorPage"; import Form from "../Form/Form"; import { HCXPolicyModel } from "../HCX/models"; import HCXPolicyValidator from "../HCX/validators"; @@ -929,7 +929,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { }; if (!isLoading && facilityId && facilityObject && !PatientRegisterAuth()) { - return ; + return ; } return ( diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000000..d736932ea06 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +

+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 00000000000..5bd1b4bfdf4 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000000..2de761f037d --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000000..44912aff543 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000000..db72e0ed963 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> +)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 00000000000..2aa54478867 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +