From 4b8b4a4f23d8e15898f779fb2bbefd3b4b99ad03 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Fri, 8 Nov 2024 10:38:43 +0530 Subject: [PATCH 01/99] Update dependencies (#9049) --- .devcontainer/devcontainer.json | 2 +- .husky/_/husky.sh | 36 -- .husky/pre-commit | 3 - .node-version | 2 +- .nvmrc | 1 - Dockerfile | 5 +- package-lock.json | 939 ++++++++++++++++---------------- package.json | 89 +-- 8 files changed, 522 insertions(+), 555 deletions(-) delete mode 100644 .husky/_/husky.sh delete mode 100644 .nvmrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 40e5259b1c6..517bcf98d88 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node { "name": "care_fe", - "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye", + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", "features": { "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { "moby": true, diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh deleted file mode 100644 index cec959a6b9d..00000000000 --- a/.husky/_/husky.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env sh -if [ -z "$husky_skip_init" ]; then - debug () { - if [ "$HUSKY_DEBUG" = "1" ]; then - echo "husky (debug) - $1" - fi - } - - readonly hook_name="$(basename -- "$0")" - debug "starting $hook_name..." - - if [ "$HUSKY" = "0" ]; then - debug "HUSKY env variable is set to 0, skipping hook" - exit 0 - fi - - if [ -f ~/.huskyrc ]; then - debug "sourcing ~/.huskyrc" - . ~/.huskyrc - fi - - readonly husky_skip_init=1 - export husky_skip_init - sh -e "$0" "$@" - exitCode="$?" - - if [ $exitCode != 0 ]; then - echo "husky - $hook_name hook exited with code $exitCode (error)" - fi - - if [ $exitCode = 127 ]; then - echo "husky - command not found in PATH=$PATH" - fi - - exit $exitCode -fi diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af219892f..2312dc587f6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - npx lint-staged diff --git a/.node-version b/.node-version index 85aee5a534c..92f279e3e66 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20 \ No newline at end of file +v22 \ No newline at end of file diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 2edeafb09db..00000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -20 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4ab5ed7dbc0..bf9997b55e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ #build-stage -FROM --platform=$BUILDPLATFORM node:20-buster-slim as build-stage +FROM --platform=$BUILDPLATFORM node:22-bookworm-slim as build-stage WORKDIR /app @@ -7,9 +7,6 @@ ENV NODE_OPTIONS="--max-old-space-size=4096" RUN apt-get update && apt-get install -y git -RUN if [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "arm64" ]; then apt-get install -y python3-dev make g++; fi - - COPY package.json package-lock.json ./ RUN npm install diff --git a/package-lock.json b/package-lock.json index e46a726b69c..d1359528fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,104 +12,105 @@ "apps/*" ], "dependencies": { - "@fontsource/figtree": "^5.1.0", - "@googlemaps/react-wrapper": "^1.1.35", + "@fontsource/figtree": "^5.1.1", + "@googlemaps/react-wrapper": "^1.1.42", "@googlemaps/typescript-guards": "^2.0.3", - "@headlessui/react": "^2.1.2", + "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", - "@sentry/browser": "^8.33.0", + "@sentry/browser": "^8.37.1", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", - "browserslist": "^4.24.0", + "browserslist": "^4.24.2", "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cross-env": "^7.0.3", - "cypress": "^13.14.2", - "dayjs": "^1.11.11", + "cypress": "^13.15.2", + "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", "events": "^3.3.0", "hi-profiles": "^1.1.0", - "i18next": "^23.11.4", - "i18next-browser-languagedetector": "^7.2.1", + "i18next": "^23.16.4", + "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", "lodash-es": "^4.17.21", - "postcss-loader": "^7.3.3", + "postcss-loader": "^8.1.1", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", - "react-i18next": "^15.0.2", + "react-i18next": "^15.1.1", "react-infinite-scroll-component": "^6.1.0", - "react-pdf": "^9.1.0", + "react-pdf": "^9.1.1", "react-webcam": "^7.2.0", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", "xlsx": "^0.18.5" }, "devDependencies": { "@tailwindcss/container-queries": "^0.1.1", - "@tailwindcss/forms": "^0.5.7", - "@tailwindcss/typography": "^0.5.13", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/dompurify": "^3.0.5", "@types/events": "^3.0.3", - "@types/google.maps": "^3.55.8", + "@types/google.maps": "^3.58.1", "@types/jsdom": "^21.1.7", "@types/lodash-es": "^4.17.12", - "@types/node": "^22.7.4", + "@types/node": "^22.9.0", "@types/qrcode.react": "^1.0.5", - "@types/react": "^18.3.11", + "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-csv": "^1.1.10", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/react-google-recaptcha": "^2.1.9", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.18.0", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.19", - "cypress-localstorage-commands": "^2.2.5", - "cypress-split": "^1.23.2", + "@vitejs/plugin-react-swc": "^3.7.1", + "autoprefixer": "^10.4.20", + "cypress-localstorage-commands": "^2.2.6", + "cypress-split": "^1.24.5", "dompurify": "^3.1.7", "dotenv": "^16.4.5", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-i18next": "^6.0.9", + "eslint-plugin-i18next": "^6.1.0", "eslint-plugin-mdx": "^3.1.5", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.35.0", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^8.0.3", + "husky": "^9.1.6", "jsdom": "^25.0.1", - "lint-staged": "^13.2.3", + "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", - "marked": "^14.1.3", - "postcss": "^8.4.38", + "marked": "^14.1.4", + "postcss": "^8.4.47", "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.5", - "snyk": "^1.1291.0", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.5", - "uuid": "^10.0.0", - "vite": "^5.2.11", - "vite-plugin-checker": "^0.6.4", - "vite-plugin-pwa": "^0.20.0", - "vite-plugin-static-copy": "^1.0.6" + "prettier-plugin-tailwindcss": "^0.6.8", + "snyk": "^1.1294.0", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "uuid": "^11.0.2", + "vite": "^5.4.10", + "vite-plugin-checker": "^0.8.0", + "vite-plugin-pwa": "^0.20.5", + "vite-plugin-static-copy": "^2.0.0" }, "engines": { - "node": ">=20.12.0" + "node": ">=22.11.0" } }, "apps/care_livekit_fe": { @@ -1955,9 +1956,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", @@ -1975,7 +1976,7 @@ "performance-now": "^2.1.0", "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -2465,7 +2466,6 @@ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2490,7 +2490,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2502,7 +2501,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2516,7 +2514,6 @@ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2615,9 +2612,9 @@ "license": "Apache-2.0" }, "node_modules/@headlessui/react": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.1.10.tgz", - "integrity": "sha512-6mLa2fjMDAFQi+/R10B+zU3edsUk/MDtENB2zHho0lqKU1uzhAfJLUduWds4nCo8wbl3vULtC5rJfZAQ1yqIng==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", + "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.26.16", @@ -2629,8 +2626,8 @@ "node": ">=10" }, "peerDependencies": { - "react": "^18", - "react-dom": "^18" + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "node_modules/@hello-pangea/dnd": { @@ -2659,7 +2656,6 @@ "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -2675,7 +2671,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2687,7 +2682,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2701,7 +2695,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -2716,8 +2709,7 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2822,6 +2814,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2930,22 +2923,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/config/node_modules/ci-info": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", - "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@npmcli/config/node_modules/ini": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", @@ -3502,12 +3479,12 @@ } }, "node_modules/@radix-ui/react-icons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", - "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.1.tgz", + "integrity": "sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ==", "license": "MIT", "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x" + "react": "^16.x || ^17.x || ^18.x || ^19.x" } }, "node_modules/@radix-ui/react-id": { @@ -4346,58 +4323,58 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz", - "integrity": "sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz", + "integrity": "sha512-OSR/V5GCsSCG7iapWtXCT/y22uo3HlawdEgfM1NIKk1mkP15UyGQtGEzZDdih2H+SNuX1mp9jQLTjr5FFp1A5w==", "license": "MIT", "dependencies": { - "@sentry/core": "8.34.0", - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry/core": "8.37.1", + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.34.0.tgz", - "integrity": "sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.37.1.tgz", + "integrity": "sha512-Se25NXbSapgS2S+JssR5YZ48b3OY4UGmAuBOafgnMW91LXMxRNWRbehZuNUmjjHwuywABMxjgu+Yp5uJDATX+g==", "license": "MIT", "dependencies": { - "@sentry/core": "8.34.0", - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry/core": "8.37.1", + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.34.0.tgz", - "integrity": "sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.37.1.tgz", + "integrity": "sha512-E/Plhisk/pXJjOdOU12sg8m/APTXTA21iEniidP6jW3/+O0tD/H/UovEqa4odNTqxPMa798xHQSQNt5loYiaLA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.34.0", - "@sentry/core": "8.34.0", - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry-internal/browser-utils": "8.37.1", + "@sentry/core": "8.37.1", + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz", - "integrity": "sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.37.1.tgz", + "integrity": "sha512-1JLAaPtn1VL5vblB0BMELFV0D+KUm/iMGsrl4/JpRm0Ws5ESzQl33DhXVv1IX/ZAbx9i14EjR7MG9+Hj70tieQ==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "8.34.0", - "@sentry/core": "8.34.0", - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry-internal/replay": "8.37.1", + "@sentry/core": "8.37.1", + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" @@ -4456,31 +4433,31 @@ } }, "node_modules/@sentry/browser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.34.0.tgz", - "integrity": "sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.37.1.tgz", + "integrity": "sha512-5ym+iGiIpjIKKpMWi9S3/tXh9xneS+jqxwRTJqed3cb8i4ydfMAAP8sM3U8xMCWWABpWyIUW+fpewC0tkhE1aQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.34.0", - "@sentry-internal/feedback": "8.34.0", - "@sentry-internal/replay": "8.34.0", - "@sentry-internal/replay-canvas": "8.34.0", - "@sentry/core": "8.34.0", - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry-internal/browser-utils": "8.37.1", + "@sentry-internal/feedback": "8.37.1", + "@sentry-internal/replay": "8.37.1", + "@sentry-internal/replay-canvas": "8.37.1", + "@sentry/core": "8.37.1", + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/core": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.34.0.tgz", - "integrity": "sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.37.1.tgz", + "integrity": "sha512-82csXby589iDupM3VgCHJeWZagUyEEaDnbFcoZ/Z91QX2Sjq8FcF5OsforoXjw09i0XTFqlkFAnQVpDBmMXcpQ==", "license": "MIT", "dependencies": { - "@sentry/types": "8.34.0", - "@sentry/utils": "8.34.0" + "@sentry/types": "8.37.1", + "@sentry/utils": "8.37.1" }, "engines": { "node": ">=14.18" @@ -4594,21 +4571,21 @@ } }, "node_modules/@sentry/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.34.0.tgz", - "integrity": "sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.37.1.tgz", + "integrity": "sha512-ryMOTROLSLINKFEbHWvi7GigNrsQhsaScw2NddybJGztJQ5UhxIGESnxGxWCufBmWFDwd7+5u0jDPCVUJybp7w==", "license": "MIT", "engines": { "node": ">=14.18" } }, "node_modules/@sentry/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==", + "version": "8.37.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.37.1.tgz", + "integrity": "sha512-Qtn2IfpII12K17txG/ZtTci35XYjYi4CxbQ3j7nXY7toGv/+MqPXwV5q2i9g94XaSXlE5Wy9/hoCZoZpZs/djA==", "license": "MIT", "dependencies": { - "@sentry/types": "8.34.0" + "@sentry/types": "8.37.1" }, "engines": { "node": ">=14.18" @@ -5146,6 +5123,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -5196,6 +5174,7 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@types/lodash": { @@ -5240,12 +5219,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/prop-types": { @@ -5266,9 +5246,9 @@ } }, "node_modules/@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5585,8 +5565,7 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.7.1", @@ -5667,6 +5646,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -5678,6 +5658,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { @@ -5685,6 +5666,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { @@ -5692,6 +5674,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -5699,6 +5682,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", @@ -5711,6 +5695,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { @@ -5718,6 +5703,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5731,6 +5717,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -5741,6 +5728,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "license": "Apache-2.0", + "optional": true, "peer": true, "dependencies": { "@xtuc/long": "4.2.2" @@ -5751,6 +5739,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { @@ -5758,6 +5747,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5775,6 +5765,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5789,6 +5780,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5802,6 +5794,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5817,6 +5810,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", @@ -5828,6 +5822,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "license": "BSD-3-Clause", + "optional": true, "peer": true }, "node_modules/@xtuc/long": { @@ -5835,6 +5830,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0", + "optional": true, "peer": true }, "node_modules/@yudiel/react-qr-scanner": { @@ -5862,6 +5858,7 @@ "version": "8.13.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5875,6 +5872,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "license": "MIT", + "optional": true, "peer": true, "peerDependencies": { "acorn": "^8" @@ -5942,8 +5940,8 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5960,6 +5958,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", + "optional": true, "peer": true, "peerDependencies": { "ajv": "^6.9.1" @@ -6576,9 +6575,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -6595,10 +6594,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -6671,6 +6670,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "devOptional": true, "license": "MIT" }, "node_modules/cachedir": { @@ -6917,15 +6917,16 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", + "optional": true, "peer": true, "engines": { "node": ">=6.0" } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", "funding": [ { "type": "github", @@ -7192,15 +7193,15 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -7322,13 +7323,13 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz", - "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==", + "version": "13.15.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", + "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.4", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -7339,6 +7340,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -7353,7 +7355,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -7368,6 +7369,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -7392,9 +7394,9 @@ } }, "node_modules/cypress-split": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/cypress-split/-/cypress-split-1.24.4.tgz", - "integrity": "sha512-sKBFQB659Ss5B08GSESeMKXBEpPD2wKNGQR1HYbKT98rnM1x5E+MugzcicqEdsK4T2Ng0feuuZQkz8XniOA18A==", + "version": "1.24.5", + "resolved": "https://registry.npmjs.org/cypress-split/-/cypress-split-1.24.5.tgz", + "integrity": "sha512-7c58IyRtT79wSffrJ78pbKtvhYsjhF6OD5Jhe/32mJ6ue9YFocHIy8D2LM5imM6UcDri6pPMROLMlIyxPElfIg==", "dev": true, "license": "MIT", "dependencies": { @@ -7635,8 +7637,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -7968,7 +7969,6 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8095,6 +8095,7 @@ "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "devOptional": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -8130,6 +8131,28 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -8280,6 +8303,7 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/es-object-atoms": { @@ -8436,7 +8460,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8622,9 +8645,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", - "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", "dev": true, "license": "MIT", "dependencies": { @@ -8633,7 +8656,7 @@ "array.prototype.flatmap": "^1.3.2", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", + "es-iterator-helpers": "^1.1.0", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", @@ -8655,16 +8678,16 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", + "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { @@ -8720,7 +8743,6 @@ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -8751,7 +8773,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8763,7 +8784,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8809,7 +8829,6 @@ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -8821,8 +8840,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8834,6 +8853,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -9019,6 +9039,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -9026,8 +9047,7 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-shuffle": { "version": "6.1.0", @@ -9094,7 +9114,6 @@ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -9268,7 +9287,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9286,7 +9304,6 @@ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -9301,8 +9318,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.3", @@ -9541,6 +9557,19 @@ "node": ">=18" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9681,6 +9710,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "license": "BSD-2-Clause", + "optional": true, "peer": true }, "node_modules/glob/node_modules/minimatch": { @@ -9738,7 +9768,6 @@ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -10051,25 +10080,25 @@ "license": "Unlicense" }, "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, "license": "MIT", "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" } }, "node_modules/i18next": { - "version": "23.16.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.0.tgz", - "integrity": "sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==", + "version": "23.16.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.4.tgz", + "integrity": "sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==", "funding": [ { "type": "individual", @@ -10090,9 +10119,9 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz", - "integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" @@ -10197,7 +10226,6 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.19" } @@ -10410,18 +10438,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -10974,6 +10990,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -11085,19 +11102,6 @@ "node": ">= 14" } }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/jsdom/node_modules/tr46": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", @@ -11153,8 +11157,7 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11172,16 +11175,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT", - "peer": true + "devOptional": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -11261,7 +11263,6 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -11301,7 +11302,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -11336,44 +11336,44 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", - "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", + "version": "15.2.10", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", + "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "5.3.0", - "commander": "11.0.0", - "debug": "4.3.4", - "execa": "7.2.0", - "lilconfig": "2.1.0", - "listr2": "6.6.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.1" + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18.12.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, "node_modules/lint-staged/node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^1.0.2" + "environment": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11419,118 +11419,100 @@ } }, "node_modules/lint-staged/node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/lint-staged/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true, "license": "MIT" }, "node_modules/lint-staged/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/lint-staged/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=14.18.0" + "node": ">=16.17.0" } }, "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { @@ -11559,64 +11541,88 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lint-staged/node_modules/listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^3.1.0", + "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/lint-staged/node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "node_modules/lint-staged/node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" + "get-east-asian-width": "^1.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/lint-staged/node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=8.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/lint-staged/node_modules/mimic-fn": { @@ -11632,13 +11638,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, "node_modules/lint-staged/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -11685,48 +11684,51 @@ } }, "node_modules/lint-staged/node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lint-staged/node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/lint-staged/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -11745,18 +11747,18 @@ } }, "node_modules/lint-staged/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11791,17 +11793,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/lint-staged/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/listr2": { @@ -11868,6 +11875,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "license": "MIT", + "optional": true, "peer": true, "engines": { "node": ">=6.11.5" @@ -11925,7 +11933,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -12149,9 +12156,9 @@ } }, "node_modules/marked": { - "version": "14.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", - "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", "dev": true, "license": "MIT", "bin": { @@ -13698,6 +13705,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", @@ -13957,6 +13977,7 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT", + "optional": true, "peer": true }, "node_modules/node-fetch": { @@ -14294,7 +14315,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -14319,7 +14339,6 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -14336,7 +14355,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -14436,7 +14454,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14497,6 +14514,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14758,25 +14776,34 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "license": "MIT", "dependencies": { - "cosmiconfig": "^8.3.5", + "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/postcss-nested": { @@ -14901,7 +14928,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -15083,12 +15109,6 @@ "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "license": "MIT" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -15103,6 +15123,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -15132,12 +15153,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -15186,6 +15201,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -15268,9 +15284,9 @@ } }, "node_modules/react-i18next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.3.tgz", - "integrity": "sha512-BlO1P+oLKjjIxDBQ0GkAIMacgjfMbnvops+3Y5nZXF7UJ99v4KCWr0Na1azJXC8AMiNWp4kgUcFCJM7U9ZsUDg==", + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.1.tgz", + "integrity": "sha512-R/Vg9wIli2P3FfeI8o1eNJUJue5LWpFsQePCHdQDmX0Co3zkr6kdT8gAseb/yGeWbNz1Txc4bKDQuZYsC0kQfw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -15959,12 +15975,6 @@ "node": ">=10.13.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -16336,6 +16346,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -16408,6 +16419,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -16643,9 +16655,9 @@ "license": "MIT" }, "node_modules/snyk": { - "version": "1.1293.1", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1293.1.tgz", - "integrity": "sha512-CnbNrsEUMGfajfJ5/03BIgx1ixWKr9Kk+9xDw6sZqKy4K5K01DkyUp/V+WjbCfjr0li9+aE7u70s276KEOuiNA==", + "version": "1.1294.0", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1294.0.tgz", + "integrity": "sha512-4RBj3Lfccz5+6L2Kw9bt7icF+ex3antwt9PkSl2oEulI7mgqvc8VUFLnezg8c6PY60IPM9DrSSmNjXBac10I3Q==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -16664,6 +16676,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16682,6 +16695,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "devOptional": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -17057,7 +17071,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -17326,6 +17339,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -17412,6 +17426,7 @@ "version": "5.36.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -17431,6 +17446,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", @@ -17465,6 +17481,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "devOptional": true, "license": "MIT" }, "node_modules/text-table": { @@ -17472,8 +17489,7 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/thenify": { "version": "3.3.1", @@ -17572,7 +17588,6 @@ "version": "6.1.58", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", - "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^6.1.58" @@ -17585,7 +17600,6 @@ "version": "6.1.58", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==", - "dev": true, "license": "MIT" }, "node_modules/tmp": { @@ -17626,27 +17640,15 @@ "license": "MIT" }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tr46": { @@ -17655,6 +17657,15 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -17760,7 +17771,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -17774,7 +17784,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -17926,6 +17935,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "devOptional": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -18320,22 +18330,12 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/use-callback-ref": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", @@ -18414,9 +18414,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -18424,7 +18424,7 @@ ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/uvu": { @@ -18664,9 +18664,9 @@ } }, "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -18724,9 +18724,9 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz", - "integrity": "sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.8.0.tgz", + "integrity": "sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==", "dev": true, "license": "MIT", "dependencies": { @@ -18738,7 +18738,6 @@ "fast-glob": "^3.2.7", "fs-extra": "^11.1.0", "npm-run-path": "^4.0.1", - "semver": "^7.5.0", "strip-ansi": "^6.0.0", "tiny-invariant": "^1.1.0", "vscode-languageclient": "^7.0.0", @@ -18750,6 +18749,7 @@ "node": ">=14.16" }, "peerDependencies": { + "@biomejs/biome": ">=1.7", "eslint": ">=7", "meow": "^9.0.0", "optionator": "^0.9.1", @@ -18758,9 +18758,12 @@ "vite": ">=2.0.0", "vls": "*", "vti": "*", - "vue-tsc": ">=1.3.9" + "vue-tsc": "~2.1.6" }, "peerDependenciesMeta": { + "@biomejs/biome": { + "optional": true + }, "eslint": { "optional": true }, @@ -18857,9 +18860,9 @@ } }, "node_modules/vite-plugin-static-copy": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.6.tgz", - "integrity": "sha512-3uSvsMwDVFZRitqoWHj0t4137Kz7UynnJeq1EZlRW7e25h2068fyIZX4ORCCOAkfp1FklGxJNVJBkBOD+PZIew==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.0.0.tgz", + "integrity": "sha512-b/quFjTUa/RY9t3geIyeeT2GtWEoRI0GawYFFjys5iMLGgVP638NTGu0RoMjwmi8MoZZ3BQw4OQvb1GpVcXZDA==", "dev": true, "license": "MIT", "dependencies": { @@ -19457,6 +19460,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -19487,6 +19491,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@types/estree": "^1.0.5", @@ -19534,6 +19539,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "license": "MIT", + "optional": true, "peer": true, "engines": { "node": ">=10.13.0" @@ -19544,6 +19550,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", + "optional": true, "peer": true, "dependencies": { "esrecurse": "^4.3.0", @@ -19558,6 +19565,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", + "optional": true, "peer": true, "engines": { "node": ">=4.0" @@ -19741,7 +19749,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -20350,11 +20357,14 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", "dev": true, "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -20375,7 +20385,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index a84fdb9ac13..cc088dd125f 100644 --- a/package.json +++ b/package.json @@ -44,108 +44,109 @@ "cypress:run": "cross-env NODE_ENV=development cypress run", "cypress:run:gui": "cross-env NODE_ENV=development cypress run --headed", "cypress:install": "cross-env NODE_ENV=development cypress install", - "prepare": "husky install", + "prepare": "husky", "lint": "eslint ./src", "lint-fix": "eslint ./src --fix", "format": "prettier ./src --write", "sort-locales": "node ./scripts/sort-locales.js" }, "dependencies": { - "@fontsource/figtree": "^5.1.0", - "@googlemaps/react-wrapper": "^1.1.35", + "@fontsource/figtree": "^5.1.1", + "@googlemaps/react-wrapper": "^1.1.42", "@googlemaps/typescript-guards": "^2.0.3", - "@headlessui/react": "^2.1.2", + "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", - "@sentry/browser": "^8.33.0", + "@sentry/browser": "^8.37.1", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", - "browserslist": "^4.24.0", + "browserslist": "^4.24.2", "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cross-env": "^7.0.3", - "cypress": "^13.14.2", - "dayjs": "^1.11.11", + "cypress": "^13.15.2", + "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", "events": "^3.3.0", "hi-profiles": "^1.1.0", - "i18next": "^23.11.4", - "i18next-browser-languagedetector": "^7.2.1", + "i18next": "^23.16.4", + "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", "lodash-es": "^4.17.21", - "postcss-loader": "^7.3.3", + "postcss-loader": "^8.1.1", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", - "react-i18next": "^15.0.2", + "react-i18next": "^15.1.1", "react-infinite-scroll-component": "^6.1.0", - "react-pdf": "^9.1.0", + "react-pdf": "^9.1.1", "react-webcam": "^7.2.0", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", "xlsx": "^0.18.5" }, "devDependencies": { "@tailwindcss/container-queries": "^0.1.1", - "@tailwindcss/forms": "^0.5.7", - "@tailwindcss/typography": "^0.5.13", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/dompurify": "^3.0.5", "@types/events": "^3.0.3", - "@types/google.maps": "^3.55.8", + "@types/google.maps": "^3.58.1", "@types/jsdom": "^21.1.7", "@types/lodash-es": "^4.17.12", - "@types/node": "^22.7.4", + "@types/node": "^22.9.0", "@types/qrcode.react": "^1.0.5", - "@types/react": "^18.3.11", + "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-csv": "^1.1.10", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/react-google-recaptcha": "^2.1.9", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.18.0", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.19", - "cypress-localstorage-commands": "^2.2.5", - "cypress-split": "^1.23.2", + "@vitejs/plugin-react-swc": "^3.7.1", + "autoprefixer": "^10.4.20", + "cypress-localstorage-commands": "^2.2.6", + "cypress-split": "^1.24.5", "dompurify": "^3.1.7", "dotenv": "^16.4.5", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-i18next": "^6.0.9", + "eslint-plugin-i18next": "^6.1.0", "eslint-plugin-mdx": "^3.1.5", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.35.0", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^8.0.3", + "husky": "^9.1.6", "jsdom": "^25.0.1", - "lint-staged": "^13.2.3", + "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", - "marked": "^14.1.3", - "postcss": "^8.4.38", + "marked": "^14.1.4", + "postcss": "^8.4.47", "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.5", - "snyk": "^1.1291.0", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.5", - "uuid": "^10.0.0", - "vite": "^5.2.11", - "vite-plugin-checker": "^0.6.4", - "vite-plugin-pwa": "^0.20.0", - "vite-plugin-static-copy": "^1.0.6" + "prettier-plugin-tailwindcss": "^0.6.8", + "snyk": "^1.1294.0", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "uuid": "^11.0.2", + "vite": "^5.4.10", + "vite-plugin-checker": "^0.8.0", + "vite-plugin-pwa": "^0.20.5", + "vite-plugin-static-copy": "^2.0.0" }, "browserslist": { "production": [ @@ -161,7 +162,7 @@ }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ - "prettier --write --ignore-unknown --plugin prettier-plugin-tailwindcss", + "prettier --write --ignore-unknown --plugin prettier-plugin-tailwindcss --plugin @trivago/prettier-plugin-sort-imports", "eslint --fix", "git update-index --again" ], @@ -170,7 +171,7 @@ ] }, "engines": { - "node": ">=20.12.0" + "node": ">=22.11.0" }, - "packageManager": "npm@10.5.0" + "packageManager": "npm@10.9.0" } From 66ef112acf365bca2145e37de690bbdd8b35f3be Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 8 Nov 2024 20:11:58 +0530 Subject: [PATCH 02/99] Update setup-care-apps scripts to remove existing apps that are not configured in env if any (#9052) --- .gitignore | 1 + scripts/setup-care-apps.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.gitignore b/.gitignore index 000a965c44a..3ae96d927fb 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ cypress/fixtures/token.json # Care Apps /apps/* src/pluginMap.ts +/apps_backup/* \ No newline at end of file diff --git a/scripts/setup-care-apps.js b/scripts/setup-care-apps.js index 870c73bd247..5f29678c1db 100644 --- a/scripts/setup-care-apps.js +++ b/scripts/setup-care-apps.js @@ -53,6 +53,36 @@ const installApp = (app) => { ); }; +const backupDir = path.join(__dirname, "..", "apps_backup"); + +// Create backup directory if needed +if (!fs.existsSync(backupDir)) { + fs.mkdirSync(backupDir); +} + +try { + fs.readdirSync(appsDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + .filter( + (dir) => + !appsConfig.map((app) => app.package.split("/")[1]).includes(dir), + ) + .forEach((unusedApp) => { + const appPath = path.join(appsDir, unusedApp); + const backupPath = path.join(backupDir, `${unusedApp}_${Date.now()}`); + console.log(`Backing up '${unusedApp}' to ${backupPath}`); + fs.cpSync(appPath, backupPath, { recursive: true }); + console.log( + `Removing existing app '${unusedApp}' as it is not configured.`, + ); + fs.rmSync(appPath, { recursive: true, force: true }); + }); +} catch (error) { + console.error("Error during cleanup:", error); + process.exit(1); +} + // Clone or pull care apps appsConfig.forEach((app) => { const appDir = path.join(appsDir, app.package.split("/")[1]); From 6bd2aa8f6511b7e0d0fa88afea69e34a9a8968dc Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Sat, 9 Nov 2024 00:02:04 +0530 Subject: [PATCH 03/99] Include icons used in enabled apps while treeshaking the icons (#9068) --- plugins/treeShakeCareIcons.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/treeShakeCareIcons.ts b/plugins/treeShakeCareIcons.ts index 131889c0f44..3ce077f5d5f 100644 --- a/plugins/treeShakeCareIcons.ts +++ b/plugins/treeShakeCareIcons.ts @@ -1,7 +1,7 @@ -import { Plugin } from "vite"; import * as fs from "fs"; -import * as path from "path"; import { globSync } from "glob"; +import * as path from "path"; +import { Plugin } from "vite"; /** * Interface defining options for the treeShakeUniconPathsPlugin. @@ -48,7 +48,7 @@ export function treeShakeCareIcons( } // Finds all used icon names within the project's source files (`.tsx` or `.res` extensions). function getAllUsedIconNames() { - const files = globSync(path.resolve(rootDir, "src/**/*.{tsx,res}")); + const files = globSync(path.resolve(rootDir, "{apps,src}/**/*.{tsx,res}")); const usedIconsArray: string[] = []; files.forEach((file) => { From cc368a9d83a334846e1caa48ad1257e10d695e09 Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan <40040905+Jacobjeevan@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:04:24 +0530 Subject: [PATCH 04/99] Avatar in UserAutoComplete (#8967) --- cypress/e2e/assets_spec/AssetHomepage.cy.ts | 11 +++-- cypress/e2e/assets_spec/AssetsCreation.cy.ts | 5 +- cypress/e2e/assets_spec/AssetsManage.cy.ts | 4 +- .../e2e/facility_spec/FacilityCreation.cy.ts | 2 +- .../e2e/facility_spec/FacilityHomepage.cy.ts | 7 ++- .../e2e/facility_spec/FacilityInventory.cy.ts | 2 +- .../e2e/facility_spec/FacilityManage.cy.ts | 7 +-- .../patient_spec/PatientBedManagement.cy.ts | 2 +- .../PatientConsultationCreation.cy.ts | 10 ++-- .../patient_spec/PatientDoctorConnect.cy.ts | 1 + cypress/e2e/patient_spec/PatientFileUpload.ts | 1 + .../e2e/patient_spec/PatientLogUpdate.cy.ts | 2 +- .../patient_spec/PatientPrescription.cy.ts | 2 +- .../patient_spec/PatientRegistration.cy.ts | 6 +-- .../e2e/resource_spec/ResourcesHomepage.cy.ts | 2 +- cypress/e2e/users_spec/UsersCreation.cy.ts | 6 +-- cypress/e2e/users_spec/UsersManage.cy.ts | 2 +- .../Common/UserAutocompleteFormField.tsx | 13 +++++ .../Form/FormFields/Autocomplete.tsx | 47 +++++++++++-------- 19 files changed, 78 insertions(+), 54 deletions(-) diff --git a/cypress/e2e/assets_spec/AssetHomepage.cy.ts b/cypress/e2e/assets_spec/AssetHomepage.cy.ts index bda4abdfae3..906cd9b2edc 100644 --- a/cypress/e2e/assets_spec/AssetHomepage.cy.ts +++ b/cypress/e2e/assets_spec/AssetHomepage.cy.ts @@ -1,10 +1,11 @@ -import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; -import { AssetQRScanPage } from "../../pageobject/Asset/AssetQRScan"; -import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; -import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; +import { v4 as uuidv4 } from "uuid"; + import { AssetPage } from "../../pageobject/Asset/AssetCreation"; +import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; +import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; +import { AssetQRScanPage } from "../../pageobject/Asset/AssetQRScan"; +import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import LoginPage from "../../pageobject/Login/LoginPage"; -import { v4 as uuidv4 } from "uuid"; describe("Asset Tab", () => { const assetSearchPage = new AssetSearchPage(); diff --git a/cypress/e2e/assets_spec/AssetsCreation.cy.ts b/cypress/e2e/assets_spec/AssetsCreation.cy.ts index 16a4fd050fb..61c6fe9b517 100644 --- a/cypress/e2e/assets_spec/AssetsCreation.cy.ts +++ b/cypress/e2e/assets_spec/AssetsCreation.cy.ts @@ -1,7 +1,8 @@ -import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import { v4 as uuidv4 } from "uuid"; -import LoginPage from "../../pageobject/Login/LoginPage"; + +import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +import LoginPage from "../../pageobject/Login/LoginPage"; describe("Asset", () => { const assetPage = new AssetPage(); diff --git a/cypress/e2e/assets_spec/AssetsManage.cy.ts b/cypress/e2e/assets_spec/AssetsManage.cy.ts index 756d3b261a6..17ee99bdb62 100644 --- a/cypress/e2e/assets_spec/AssetsManage.cy.ts +++ b/cypress/e2e/assets_spec/AssetsManage.cy.ts @@ -1,8 +1,8 @@ import { AssetPage } from "../../pageobject/Asset/AssetCreation"; -import LoginPage from "../../pageobject/Login/LoginPage"; +import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; +import LoginPage from "../../pageobject/Login/LoginPage"; function addDaysToDate(numberOfDays: number) { const inputDate = new Date(); diff --git a/cypress/e2e/facility_spec/FacilityCreation.cy.ts b/cypress/e2e/facility_spec/FacilityCreation.cy.ts index 4961ae4a4cb..fff1311fdd6 100644 --- a/cypress/e2e/facility_spec/FacilityCreation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityCreation.cy.ts @@ -1,6 +1,6 @@ import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import LoginPage from "../../pageobject/Login/LoginPage"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; +import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index e6caf645f7a..bc84aea4882 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -1,11 +1,10 @@ // FacilityCreation - -import LoginPage from "../../pageobject/Login/LoginPage"; +import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; +import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import { UserPage } from "../../pageobject/Users/UserSearch"; -import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; describe("Facility Homepage Function", () => { const loginPage = new LoginPage(); diff --git a/cypress/e2e/facility_spec/FacilityInventory.cy.ts b/cypress/e2e/facility_spec/FacilityInventory.cy.ts index cdada75ee06..a9f32984efc 100644 --- a/cypress/e2e/facility_spec/FacilityInventory.cy.ts +++ b/cypress/e2e/facility_spec/FacilityInventory.cy.ts @@ -1,6 +1,6 @@ import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import LoginPage from "../../pageobject/Login/LoginPage"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; +import LoginPage from "../../pageobject/Login/LoginPage"; describe("Inventory Management Section", () => { const facilityPage = new FacilityPage(); diff --git a/cypress/e2e/facility_spec/FacilityManage.cy.ts b/cypress/e2e/facility_spec/FacilityManage.cy.ts index 1f0d2b66108..10074e81166 100644 --- a/cypress/e2e/facility_spec/FacilityManage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityManage.cy.ts @@ -1,8 +1,9 @@ -import LoginPage from "../../pageobject/Login/LoginPage"; -import FacilityManage from "../../pageobject/Facility/FacilityManage"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import { v4 as uuidv4 } from "uuid"; +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; +import FacilityManage from "../../pageobject/Facility/FacilityManage"; +import LoginPage from "../../pageobject/Login/LoginPage"; + describe("Facility Manage Functions", () => { const loginPage = new LoginPage(); const facilityManage = new FacilityManage(); diff --git a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts index 84ef2f4800e..65adf131c87 100644 --- a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts +++ b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts @@ -1,7 +1,7 @@ import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; -import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; describe("Patient swtich bed functionality", () => { const loginPage = new LoginPage(); diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index ee38b24d746..4c84f7fad8f 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -1,12 +1,12 @@ import LoginPage from "../../pageobject/Login/LoginPage"; -import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; -import PatientPredefined from "../../pageobject/Patient/PatientPredefined"; -import ShiftCreation from "../../pageobject/Shift/ShiftCreation"; -import PatientInvestigation from "../../pageobject/Patient/PatientInvestigation"; -import PatientTreatmentPlan from "../../pageobject/Patient/PatientTreatmentPlan"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import PatientDeathReport from "../../pageobject/Patient/PatientDeathReport"; +import PatientInvestigation from "../../pageobject/Patient/PatientInvestigation"; +import PatientPredefined from "../../pageobject/Patient/PatientPredefined"; import PatientPrescription from "../../pageobject/Patient/PatientPrescription"; +import PatientTreatmentPlan from "../../pageobject/Patient/PatientTreatmentPlan"; +import ShiftCreation from "../../pageobject/Shift/ShiftCreation"; describe("Patient Consultation in multiple combination", () => { const patientConsultationPage = new PatientConsultationPage(); diff --git a/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts b/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts index 9197cac2d90..dd626f619ce 100644 --- a/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts +++ b/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts @@ -1,4 +1,5 @@ import { DoctorConnect } from "pageobject/Patient/PatientDoctorConnect"; + import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; diff --git a/cypress/e2e/patient_spec/PatientFileUpload.ts b/cypress/e2e/patient_spec/PatientFileUpload.ts index cc94943fd6f..110631551a1 100644 --- a/cypress/e2e/patient_spec/PatientFileUpload.ts +++ b/cypress/e2e/patient_spec/PatientFileUpload.ts @@ -1,6 +1,7 @@ import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import { PatientFileUpload } from "../../pageobject/Patient/PatientFileupload"; + const loginPage = new LoginPage(); const patientPage = new PatientPage(); const patientFileUpload = new PatientFileUpload(); diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index b32990872e2..b6d7ecbc173 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -1,8 +1,8 @@ import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; -import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; import PatientInvestigation from "../../pageobject/Patient/PatientInvestigation"; +import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; import PatientPrescription from "../../pageobject/Patient/PatientPrescription"; describe("Patient Log Update in Normal, Critical and TeleIcu", () => { diff --git a/cypress/e2e/patient_spec/PatientPrescription.cy.ts b/cypress/e2e/patient_spec/PatientPrescription.cy.ts index f5febc85fdc..53e67324199 100644 --- a/cypress/e2e/patient_spec/PatientPrescription.cy.ts +++ b/cypress/e2e/patient_spec/PatientPrescription.cy.ts @@ -1,6 +1,6 @@ -import PatientPrescription from "../../pageobject/Patient/PatientPrescription"; import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; +import PatientPrescription from "../../pageobject/Patient/PatientPrescription"; const patientPrescription = new PatientPrescription(); const loginPage = new LoginPage(); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index d774a90ceb8..cb84fa06674 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -1,10 +1,10 @@ +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientPage } from "../../pageobject/Patient/PatientCreation"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import { generatePhoneNumber } from "../../pageobject/utils/constants"; -import PatientTransfer from "../../pageobject/Patient/PatientTransfer"; import PatientInsurance from "../../pageobject/Patient/PatientInsurance"; import PatientMedicalHistory from "../../pageobject/Patient/PatientMedicalHistory"; +import PatientTransfer from "../../pageobject/Patient/PatientTransfer"; +import { generatePhoneNumber } from "../../pageobject/utils/constants"; const yearOfBirth = "2001"; const isHCXEnabled = Cypress.env("ENABLE_HCX"); diff --git a/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts b/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts index 8dc526a68c3..299d753d720 100644 --- a/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts +++ b/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts @@ -1,6 +1,6 @@ +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; import ResourcePage from "../../pageobject/Resource/ResourcePage"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; describe("Resource Page", () => { let createdResource: string; diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index 683a2131b2f..f495a136d97 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -1,11 +1,11 @@ -import LoginPage from "../../pageobject/Login/LoginPage"; import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import { UserPage } from "../../pageobject/Users/UserSearch"; +import LoginPage from "../../pageobject/Login/LoginPage"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; +import { UserPage } from "../../pageobject/Users/UserSearch"; import { - generatePhoneNumber, generateEmergencyPhoneNumber, + generatePhoneNumber, } from "../../pageobject/utils/constants"; describe("User Creation", () => { diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 98c2d564f6d..9ed4fe34ec7 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -1,7 +1,7 @@ import LoginPage from "../../pageobject/Login/LoginPage"; -import { UserPage } from "../../pageobject/Users/UserSearch"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; +import { UserPage } from "../../pageobject/Users/UserSearch"; describe("Manage User", () => { const loginPage = new LoginPage(); diff --git a/src/components/Common/UserAutocompleteFormField.tsx b/src/components/Common/UserAutocompleteFormField.tsx index f6e10e14870..47381414221 100644 --- a/src/components/Common/UserAutocompleteFormField.tsx +++ b/src/components/Common/UserAutocompleteFormField.tsx @@ -19,6 +19,8 @@ import { mergeQueryOptions, } from "@/Utils/utils"; +import { Avatar } from "./Avatar"; + type BaseProps = FormFieldBaseProps & { placeholder?: string; userType?: UserRole; @@ -67,6 +69,16 @@ export default function UserAutocomplete(props: UserSearchProps) { } }, [loading, field.required, data?.results, props.noResultsError]); + const getAvatar = (option: UserBareMinimum) => { + return ( + + ); + }; + return ( `${option.user_type} - ${option.username}` } diff --git a/src/components/Form/FormFields/Autocomplete.tsx b/src/components/Form/FormFields/Autocomplete.tsx index f66eafe6e36..37486974881 100644 --- a/src/components/Form/FormFields/Autocomplete.tsx +++ b/src/components/Form/FormFields/Autocomplete.tsx @@ -5,7 +5,7 @@ import { ComboboxOption, ComboboxOptions, } from "@headlessui/react"; -import { useEffect, useState } from "react"; +import { ReactNode, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -29,6 +29,7 @@ type AutocompleteFormFieldProps = FormFieldBaseProps & { optionValue?: OptionCallback; optionDescription?: OptionCallback; optionIcon?: OptionCallback; + optionImage?: OptionCallback; optionDisabled?: OptionCallback; minQueryLength?: number; onQuery?: (query: string) => void; @@ -55,6 +56,7 @@ const AutocompleteFormField = ( placeholder={props.placeholder} optionLabel={props.optionLabel} optionIcon={props.optionIcon} + optionImage={props.optionImage} optionValue={props.optionValue} optionDescription={props.optionDescription} optionDisabled={props.optionDisabled} @@ -79,6 +81,7 @@ type AutocompleteProps = { placeholder?: string; optionLabel: OptionCallback; optionIcon?: OptionCallback; + optionImage?: OptionCallback; optionValue?: OptionCallback; optionDescription?: OptionCallback; optionDisabled?: OptionCallback; @@ -89,7 +92,6 @@ type AutocompleteProps = { isLoading?: boolean; allowRawInput?: boolean; error?: string; - avatar?: boolean; } & ( | { required?: false; @@ -124,6 +126,7 @@ export const Autocomplete = (props: AutocompleteProps) => { description, search: label.toLowerCase(), icon: props.optionIcon?.(option), + image: props.optionImage?.(option), value: props.optionValue ? props.optionValue(option) : option, disabled: props.optionDisabled?.(option), }; @@ -143,6 +146,7 @@ export const Autocomplete = (props: AutocompleteProps) => { description: undefined, search: query.toLowerCase(), icon: , + image: undefined, value: query, disabled: undefined, }, @@ -241,25 +245,28 @@ export const Autocomplete = (props: AutocompleteProps) => { disabled={option.disabled} > {({ focus }) => ( -
-
- {option.label} - {option.icon} -
- {option.description && ( -
- {option.description} +
+ {option?.image} +
+
+ {option.label} + {option.icon}
- )} + {option.description && ( +
+ {option.description} +
+ )} +
)} From 84fdffeaf67fe94eddfefee361ad0d60fc86c039 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:13:04 +0530 Subject: [PATCH 05/99] fix locale sort's path and fix format script to sort cypress dir (#9070) Co-authored-by: rithviknishad --- cypress/e2e/facility_spec/FacilityLocation.cy.ts | 9 +++++---- cypress/e2e/users_spec/UsersProfile.cy.ts | 2 +- cypress/pageobject/Patient/PatientPredefined.ts | 5 ++--- cypress/tsconfig.json | 15 ++++++++++++--- package.json | 6 +++--- scripts/sort-locales.js | 2 +- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cypress/e2e/facility_spec/FacilityLocation.cy.ts b/cypress/e2e/facility_spec/FacilityLocation.cy.ts index d29ee95e873..b700f5c75ae 100644 --- a/cypress/e2e/facility_spec/FacilityLocation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityLocation.cy.ts @@ -1,10 +1,11 @@ +import { v4 as uuidv4 } from "uuid"; + import { AssetPage } from "../../pageobject/Asset/AssetCreation"; -import { UserCreationPage } from "../../pageobject/Users/UserCreation"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; -import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; +import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; -import { v4 as uuidv4 } from "uuid"; +import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; +import { UserCreationPage } from "../../pageobject/Users/UserCreation"; describe("Location Management Section", () => { const assetPage = new AssetPage(); diff --git a/cypress/e2e/users_spec/UsersProfile.cy.ts b/cypress/e2e/users_spec/UsersProfile.cy.ts index 3073cebe7ce..35771024180 100644 --- a/cypress/e2e/users_spec/UsersProfile.cy.ts +++ b/cypress/e2e/users_spec/UsersProfile.cy.ts @@ -1,6 +1,6 @@ import LoginPage from "../../pageobject/Login/LoginPage"; -import UserProfilePage from "../../pageobject/Users/UserProfilePage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; +import UserProfilePage from "../../pageobject/Users/UserProfilePage"; describe("Manage User Profile", () => { const loginPage = new LoginPage(); diff --git a/cypress/pageobject/Patient/PatientPredefined.ts b/cypress/pageobject/Patient/PatientPredefined.ts index f0a005ec97b..a3eb41cb86c 100644 --- a/cypress/pageobject/Patient/PatientPredefined.ts +++ b/cypress/pageobject/Patient/PatientPredefined.ts @@ -1,11 +1,10 @@ // PatientPredefined.js - -import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; import PatientMedicalHistory from "../../pageobject/Patient/PatientMedicalHistory"; import { - generatePhoneNumber, generateEmergencyPhoneNumber, + generatePhoneNumber, } from "../utils/constants"; class PatientPredefined { diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 114000b60f0..3fbb7b08f69 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -2,9 +2,18 @@ "compilerOptions": { "baseUrl": "./", "target": "es5", - "lib": ["es5", "dom", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020"], + "lib": [ + "es5", + "dom", + "es2015", + "es2016", + "es2017", + "es2018", + "es2019", + "es2020" + ], "typeRoots": ["./support"], "resolveJsonModule": true }, - "include": ["**/*.cy.ts", "support/commands.ts","**/*.ts"], -} \ No newline at end of file + "include": ["**/*.cy.ts", "support/commands.ts", "**/*.ts"] +} diff --git a/package.json b/package.json index cc088dd125f..6b2273e341f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "prepare": "husky", "lint": "eslint ./src", "lint-fix": "eslint ./src --fix", - "format": "prettier ./src --write", + "format": "prettier ./src ./cypress --write", "sort-locales": "node ./scripts/sort-locales.js" }, "dependencies": { @@ -166,7 +166,7 @@ "eslint --fix", "git update-index --again" ], - "src/Locale/*.json": [ + "public/locale/*.json": [ "npm run sort-locales" ] }, @@ -174,4 +174,4 @@ "node": ">=22.11.0" }, "packageManager": "npm@10.9.0" -} +} \ No newline at end of file diff --git a/scripts/sort-locales.js b/scripts/sort-locales.js index adff586a93b..62c3e3d8ba1 100644 --- a/scripts/sort-locales.js +++ b/scripts/sort-locales.js @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const fs = require("fs"); -const file = "src/Locale/en.json"; +const file = "public/locale/en.json"; const data = JSON.parse(fs.readFileSync(file, "utf8")); From d84caf0c3a958273887831bb6af1c0ad61b921bc Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan <40040905+Jacobjeevan@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:09:43 +0530 Subject: [PATCH 06/99] Ventilator mode/oxygen modality data on consultation page (#8781) * Ventilator Data - Added ventilator data as table on Ventilator tab - Added Marker Area to graphs under ventilator tab * MarkerLine and MarkerArea - To do: cleanup (only keep whichever one is chosen, cleanup commented out code) * MarkLine, rm MarkArea code - Choosing markLine as that represents the data accurately (when switching to bar graph) * Rm markArea import * added overflow to table/mobile view * render most recent mode/modality on graph * log fixes * IV/NIV log text modification * Fixes and cleanup - Switched VentilatorPlot to use getDailyReports (instead of getDailyRoundsAnalyse). - Changed end_date to be based on next daily round (rather than round with ventilator data) - Pagination on top level (in the tab, instead of in Plots) - Applies to both Table and the Graphs * Cleanup - Removed filtering items in VentorTable beforehand - allowing to fetch the right end date - as well as only combining rounds with same NIV/IV/Oxygen mode/modality if there are consecutive (i.e. no round in between them) - VentilatorPlot - adjust label position for the first data point (to avoid overflow of line label into y axis labels) * Pagination Limit - Added limit of 36 (same as before for pagination in VentilatorPlot) * using date utils * improvement suggestions * Nitpick fixes * type fix * lint fix --- public/locale/en.json | 20 ++ .../ConsultationVentilatorTab.tsx | 34 ++- .../ConsultationDetails/Events/EventsList.tsx | 6 + .../Facility/Consultations/VentilatorPlot.tsx | 275 ++++++++++++++---- .../Consultations/VentilatorTable.tsx | 129 ++++++++ .../components/BinaryChronologicalChart.tsx | 2 +- .../Consultations/components/LinePlot.tsx | 32 +- .../Consultations/components/ReactEcharts.tsx | 2 + 8 files changed, 443 insertions(+), 57 deletions(-) create mode 100644 src/components/Facility/Consultations/VentilatorTable.tsx diff --git a/public/locale/en.json b/public/locale/en.json index b6ccb84de08..ef591130213 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -133,9 +133,13 @@ "ORAL_ISSUE__NO_ISSUE": "No issues", "ORAL_ISSUE__ODYNOPHAGIA": "Odynophagia", "OXYGEN_MODALITY__HIGH_FLOW_NASAL_CANNULA": "High Flow Nasal Cannula", + "OXYGEN_MODALITY__HIGH_FLOW_NASAL_CANNULA_short": "HFNC", "OXYGEN_MODALITY__NASAL_PRONGS": "Nasal Prongs", + "OXYGEN_MODALITY__NASAL_PRONGS_short": "NP", "OXYGEN_MODALITY__NON_REBREATHING_MASK": "Non Rebreathing Mask", + "OXYGEN_MODALITY__NON_REBREATHING_MASK_short": "NRM", "OXYGEN_MODALITY__SIMPLE_FACE_MASK": "Simple Face Mask", + "OXYGEN_MODALITY__SIMPLE_FACE_MASK_short": "SFM", "PRESCRIPTION_FREQUENCY_BD": "Twice daily", "PRESCRIPTION_FREQUENCY_HS": "Night only", "PRESCRIPTION_FREQUENCY_OD": "Once daily", @@ -206,12 +210,19 @@ "URINATION_FREQUENCY__NORMAL": "Normal", "VENTILATOR": "Detailed Update", "VENTILATOR_MODE__CMV": "Control Mechanical Ventilation (CMV)", + "VENTILATOR_MODE__CMV_short": "CMV", "VENTILATOR_MODE__PCV": "Pressure Control Ventilation (PCV)", + "VENTILATOR_MODE__PCV_short": "PCV", "VENTILATOR_MODE__PC_SIMV": "Pressure Controlled SIMV (PC-SIMV)", + "VENTILATOR_MODE__PC_SIMV_short": "PC-SIMV", "VENTILATOR_MODE__PSV": "C-PAP / Pressure Support Ventilation (PSV)", + "VENTILATOR_MODE__PSV_short": "C-PAP/PSV", "VENTILATOR_MODE__SIMV": "Synchronised Intermittent Mandatory Ventilation (SIMV)", + "VENTILATOR_MODE__SIMV_short": "SIMV", "VENTILATOR_MODE__VCV": "Volume Control Ventilation (VCV)", + "VENTILATOR_MODE__VCV_short": "VCV", "VENTILATOR_MODE__VC_SIMV": "Volume Controlled SIMV (VC-SIMV)", + "VENTILATOR_MODE__VC_SIMV_short": "VC-SIMV", "View Facility": "View Facility", "aadhaar_number": "Aadhaar Number", "aadhaar_number_will_not_be_stored": "Aadhaar number will not be stored by CARE", @@ -611,6 +622,7 @@ "encounter_suggestion__OP": "Out-patient visit", "encounter_suggestion__R": "Consultation", "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation", + "end_datetime": "End Date/Time", "enter_aadhaar_number": "Enter a 12-digit Aadhaar ID", "enter_aadhaar_otp": "Enter OTP sent to the registered mobile with Aadhaar", "enter_abha_address": "Enter ABHA Address", @@ -1145,6 +1157,7 @@ "spokes": "Spoke Facilities", "srf_id": "SRF ID", "staff_list": "Staff List", + "start_datetime": "Start Date/Time", "start_dosage": "Start Dosage", "state": "State", "status": "Status", @@ -1236,6 +1249,13 @@ "vacant": "Vacant", "vehicle_preference": "Vehicle preference", "vendor_name": "Vendor Name", + "ventilator_interface": "Respiratory Support Type", + "ventilator_log": "Ventilator Log", + "ventilator_modality": "Modality", + "ventilator_mode": "Ventilator Mode", + "ventilator_oxygen_modality": "Oxygen Modality", + "ventilator_oxygen_modality_oxygen_rate": "Oxygen Flow Rate", + "ventilator_spo2": "SpO₂", "verify_and_link": "Verify and Link", "verify_otp": "Verify OTP", "verify_otp_error": "Failed to verify OTP. Please try again later.", diff --git a/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx index 1ded0ba7684..b26ea6e0e53 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx @@ -1,8 +1,30 @@ +import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; import { ConsultationTabProps } from "@/components/Facility/ConsultationDetails/index"; import { VentilatorPlot } from "@/components/Facility/Consultations/VentilatorPlot"; +import VentilatorTable from "@/components/Facility/Consultations/VentilatorTable"; + +import useFilters from "@/hooks/useFilters"; + +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; export const ConsultationVentilatorTab = (props: ConsultationTabProps) => { + const { consultationId } = props; + const { qParams, Pagination, resultsPerPage } = useFilters({ limit: 36 }); + + const { loading: isLoading, data } = useQuery(routes.getDailyReports, { + pathParams: { consultationId }, + query: { + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }, + }); + + if (isLoading) { + return ; + } + return (
{ hideBack={true} breadcrumbs={false} /> - + + + {Boolean(data?.count && data.count > 0) && ( +
+ +
+ )}
); }; diff --git a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx index e3f7072391e..47c68636a1a 100644 --- a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx +++ b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx @@ -68,6 +68,12 @@ export default function EventsList({ query }: { query: QueryParams }) { } const values = Object.fromEntries(entries); + if ( + values.ventilator_interface === "INVASIVE" || + values.ventilator_interface === "NON_INVASIVE" + ) { + values.ventilator_interface += " VENTILATOR"; + } switch (item.event_type.name) { case "INTERNAL_TRANSFER": diff --git a/src/components/Facility/Consultations/VentilatorPlot.tsx b/src/components/Facility/Consultations/VentilatorPlot.tsx index 6991fd02b8d..38948f165ea 100644 --- a/src/components/Facility/Consultations/VentilatorPlot.tsx +++ b/src/components/Facility/Consultations/VentilatorPlot.tsx @@ -1,14 +1,11 @@ import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; -import Pagination from "@/components/Common/Pagination"; +import Loading from "@/components/Common/Loading"; import BinaryChronologicalChart from "@/components/Facility/Consultations/components/BinaryChronologicalChart"; import { LinePlot } from "@/components/Facility/Consultations/components/LinePlot"; -import { VentilatorPlotFields } from "@/components/Facility/models"; +import { DailyRoundsModel } from "@/components/Patient/models"; -import { PAGINATION_LIMIT } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; import { formatDateTime } from "@/Utils/utils"; /* @@ -31,48 +28,222 @@ const modality: Array = [ ]; */ -export const VentilatorPlot = (props: any) => { - const { consultationId } = props; - const [results, setResults] = useState({}); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); +interface graphDataProps { + [key: string]: { + bilateral_air_entry?: boolean; + etco2?: number; + id?: string; + ventilator_fio2?: number; + ventilator_mean_airway_pressure?: number; + ventilator_oxygen_modality_flow_rate?: number; + ventilator_oxygen_modality_oxygen_rate?: number; + ventilator_peep?: number | null; + ventilator_pip?: number; + ventilator_pressure_support?: number; + ventilator_resp_rate?: number; + ventilator_spo2?: number; + ventilator_tidal_volume?: number; + }; +} - useEffect(() => { - const fetchDailyRounds = async ( - currentPage: number, - consultationId: string, - ) => { - const { res, data } = await request(routes.dailyRoundsAnalyse, { - body: { page: currentPage, fields: VentilatorPlotFields }, - pathParams: { - consultationId, - }, +export const VentilatorPlot = ({ + dailyRoundsList, +}: { + dailyRoundsList?: DailyRoundsModel[]; +}) => { + const [results, setResults] = useState({}); + const { t } = useTranslation(); + + const getGraphData = (dailyRoundsData?: DailyRoundsModel[]) => { + const graphData: graphDataProps = {}; + const graphDataCount = dailyRoundsData?.length ?? 0; + if (dailyRoundsData) { + dailyRoundsData.forEach((currentRound: DailyRoundsModel) => { + // @ts-expect-error taken_at should always be available + graphData[currentRound.taken_at] = { + bilateral_air_entry: currentRound.bilateral_air_entry, + etco2: currentRound.etco2, + id: currentRound.id, + ventilator_fio2: currentRound.ventilator_fio2, + ventilator_mean_airway_pressure: + currentRound.ventilator_mean_airway_pressure, + ventilator_oxygen_modality_flow_rate: + currentRound.ventilator_oxygen_modality_flow_rate, + ventilator_oxygen_modality_oxygen_rate: + currentRound.ventilator_oxygen_modality_oxygen_rate, + ventilator_peep: currentRound.ventilator_peep + ? Number(currentRound.ventilator_peep) + : null, + ventilator_pip: currentRound.ventilator_pip, + ventilator_pressure_support: currentRound.ventilator_pressure_support, + ventilator_resp_rate: currentRound.ventilator_resp_rate, + ventilator_spo2: currentRound.ventilator_spo2, + ventilator_tidal_volume: currentRound.ventilator_tidal_volume, + }; }); - if (res && res.ok && data) { - setResults(data.results); - setTotalCount(data.count); - } - }; + } + return { graphData, graphDataCount }; + }; + + useEffect(() => { + const { graphData } = getGraphData(dailyRoundsList); + setResults(graphData); + }, [dailyRoundsList]); - fetchDailyRounds(currentPage, consultationId); - }, [consultationId, currentPage]); + if (!dailyRoundsList) { + return ; + } - const handlePagination = (page: number) => { - setCurrentPage(page); + const dates = Object.keys(results).map((p: string) => formatDateTime(p)); + + const getConditionAndLegend = ( + name: string, + currentRound: DailyRoundsModel, + ) => { + let condition = false; + let legend = ""; + switch (name) { + case "ventilator_pip": + case "ventilator_mean_airway_pressure": + case "ventilator_resp_rate": + case "ventilator_pressure_support": + case "ventilator_tidal_volume": + case "ventilator_peep": + condition = + (currentRound.ventilator_interface === "INVASIVE" || + currentRound.ventilator_interface === "NON_INVASIVE") && + !!currentRound.ventilator_mode; + break; + case "ventilator_fio2": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + currentRound.ventilator_oxygen_modality === "HIGH_FLOW_NASAL_CANNULA"; + break; + case "ventilator_spo2": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + (currentRound.ventilator_oxygen_modality === "NASAL_PRONGS" || + currentRound.ventilator_oxygen_modality === "SIMPLE_FACE_MASK" || + currentRound.ventilator_oxygen_modality === + "NON_REBREATHING_MASK" || + currentRound.ventilator_oxygen_modality === + "HIGH_FLOW_NASAL_CANNULA"); + break; + case "etco2": + case "ventilator_oxygen_modality_flow_rate": + condition = + !!currentRound.ventilator_mode || + !!currentRound.ventilator_oxygen_modality || + false; + break; + case "ventilator_oxygen_modality_oxygen_rate": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + (currentRound.ventilator_oxygen_modality === "NASAL_PRONGS" || + currentRound.ventilator_oxygen_modality === "SIMPLE_FACE_MASK" || + currentRound.ventilator_oxygen_modality === "NON_REBREATHING_MASK"); + break; + } + switch (currentRound.ventilator_interface) { + case "OXYGEN_SUPPORT": + legend = + t( + `OXYGEN_MODALITY__${currentRound.ventilator_oxygen_modality}_short`, + ) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__OXYGEN_SUPPORT") + + ")"; + break; + case "INVASIVE": + legend = + t(`VENTILATOR_MODE__${currentRound.ventilator_mode}_short`) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__INVASIVE") + + ")"; + break; + case "NON_INVASIVE": + legend = + t(`VENTILATOR_MODE__${currentRound.ventilator_mode}_short`) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__NON_INVASIVE") + + ")"; + break; + } + return { condition, legend }; + }; + + const getModeOrModality = (round: DailyRoundsModel) => { + const ventilatorInterface = round.ventilator_interface; + if (!ventilatorInterface) return null; + switch (ventilatorInterface) { + case "INVASIVE": + case "NON_INVASIVE": + return round.ventilator_mode; + case "OXYGEN_SUPPORT": + return round.ventilator_oxygen_modality; + default: + return null; + } }; - const dates = Object.keys(results) - .map((p: string) => formatDateTime(p)) - .reverse(); + const getMarkLineData = (name: string) => { + const markLineData = []; + if (!dailyRoundsList) return []; + let index = 0; + while (index < dailyRoundsList.length) { + const currentRound = dailyRoundsList[index]; + const { condition, legend } = getConditionAndLegend(name, currentRound); + const currentInterfaceOrModality = getModeOrModality(currentRound); + if (condition) { + const startIndex = dates.findIndex( + (element) => element === formatDateTime(currentRound.taken_at), + ); + if (startIndex !== -1) { + let nextIndex = index + 1; + while (nextIndex < dailyRoundsList.length) { + const nextRound = dailyRoundsList[nextIndex]; + const nextInterfaceOrModality = getModeOrModality(nextRound); + if ( + currentRound.ventilator_interface === + nextRound.ventilator_interface && + currentInterfaceOrModality === nextInterfaceOrModality + ) { + nextIndex += 1; + } else { + break; + } + } + const position = + startIndex === 0 ? "insideMiddleBottom" : "insideMiddleTop"; + markLineData.push({ + name: legend, + xAxis: dates[startIndex], + label: { + show: true, + position, + formatter: "{b}", + color: "#000000", + textBorderColor: "#ffffff", + textBorderWidth: 2, + }, + }); + index = nextIndex; + } else { + index += 1; + } + } else { + index += 1; + } + } + return markLineData; + }; - const yAxisData = (name: string) => { - return Object.values(results) - .map((p: any) => p[name]) - .reverse(); + const yAxisData = (name: keyof graphDataProps[string]) => { + return Object.values(results).map((p) => p[name]); }; const bilateral = Object.values(results) - .map((p: any, i) => { + .map((p, i) => { return { value: p.bilateral_air_entry, timestamp: Object.keys(results)[i], @@ -91,6 +262,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_pip")} low={12} high={30} + verticalMarkerData={getMarkLineData("ventilator_pip")} />
@@ -101,6 +273,9 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_mean_airway_pressure")} low={12} high={25} + verticalMarkerData={getMarkLineData( + "ventilator_mean_airway_pressure", + )} />
@@ -111,6 +286,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_resp_rate")} low={12} high={20} + verticalMarkerData={getMarkLineData("ventilator_resp_rate")} />
@@ -121,6 +297,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_pressure_support")} low={5} high={15} + verticalMarkerData={getMarkLineData("ventilator_pressure_support")} />
@@ -129,6 +306,7 @@ export const VentilatorPlot = (props: any) => { name="Tidal Volume" xData={dates} yData={yAxisData("ventilator_tidal_volume")} + verticalMarkerData={getMarkLineData("ventilator_tidal_volume")} />
@@ -139,6 +317,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_peep")} low={5} high={10} + verticalMarkerData={getMarkLineData("ventilator_peep")} />
@@ -149,6 +328,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_fio2")} low={21} high={60} + verticalMarkerData={getMarkLineData("ventilator_fio2")} />
@@ -159,6 +339,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_spo2")} low={90} high={100} + verticalMarkerData={getMarkLineData("ventilator_spo2")} />
@@ -169,6 +350,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("etco2")} low={35} high={45} + verticalMarkerData={getMarkLineData("etco2")} />
@@ -185,6 +367,9 @@ export const VentilatorPlot = (props: any) => { name="Oxygen Flow Rate" xData={dates} yData={yAxisData("ventilator_oxygen_modality_oxygen_rate")} + verticalMarkerData={getMarkLineData( + "ventilator_oxygen_modality_oxygen_rate", + )} />
@@ -193,20 +378,12 @@ export const VentilatorPlot = (props: any) => { name="Flow Rate" xData={dates} yData={yAxisData("ventilator_oxygen_modality_flow_rate")} + verticalMarkerData={getMarkLineData( + "ventilator_oxygen_modality_flow_rate", + )} />
- - {totalCount > PAGINATION_LIMIT && ( -
- -
- )} ); }; diff --git a/src/components/Facility/Consultations/VentilatorTable.tsx b/src/components/Facility/Consultations/VentilatorTable.tsx new file mode 100644 index 00000000000..2059f14bac0 --- /dev/null +++ b/src/components/Facility/Consultations/VentilatorTable.tsx @@ -0,0 +1,129 @@ +import { useTranslation } from "react-i18next"; + +import { compareByDateString, formatDateTime } from "@/Utils/utils"; + +import { DailyRoundsModel } from "../../Patient/models"; + +type VentilatorTableProps = { + dailyRoundsList?: DailyRoundsModel[]; +}; + +export default function VentilatorTable(props: VentilatorTableProps) { + const { t } = useTranslation(); + const { dailyRoundsList } = props; + + const VentilatorTableRow = ({ + dailyRound, + start_date, + end_date, + }: { + dailyRound: DailyRoundsModel; + start_date: string; + end_date: string; + }) => { + const getModeText = () => { + const { + ventilator_interface, + ventilator_mode, + ventilator_oxygen_modality, + } = dailyRound; + switch (ventilator_interface) { + case "INVASIVE": + case "NON_INVASIVE": + return t(`VENTILATOR_MODE__${ventilator_mode}`); + case "OXYGEN_SUPPORT": + return t(`OXYGEN_MODALITY__${ventilator_oxygen_modality}`); + default: + return null; + } + }; + return ( + + {start_date} + {end_date} + + {t(`RESPIRATORY_SUPPORT__${dailyRound?.ventilator_interface}`)} + + {getModeText()} + + ); + }; + + const getModeOrModality = (round: DailyRoundsModel) => { + const ventilatorInterface = round.ventilator_interface; + if (!ventilatorInterface) return null; + switch (ventilatorInterface) { + case "INVASIVE": + case "NON_INVASIVE": + return round.ventilator_mode; + case "OXYGEN_SUPPORT": + return round.ventilator_oxygen_modality; + default: + return null; + } + }; + + const VentilatorTableBody = (dailyRoundsList: DailyRoundsModel[]) => { + const rows = []; + for (let index = 0; index < dailyRoundsList.length; index++) { + const currentRound = dailyRoundsList[index]; + const currentInterfaceOrModality = getModeOrModality(currentRound); + if (!currentInterfaceOrModality) continue; + while (index < dailyRoundsList.length - 1) { + const nextRound = dailyRoundsList[index + 1]; + const nextInterfaceOrModality = getModeOrModality(nextRound); + if ( + nextInterfaceOrModality && + currentRound.ventilator_interface == nextRound.ventilator_interface && + currentInterfaceOrModality == nextInterfaceOrModality + ) { + index += 1; + } else { + break; + } + } + const end_date = + index + 1 < dailyRoundsList.length + ? formatDateTime(dailyRoundsList[index + 1].taken_at) + : ""; + const start_date = formatDateTime(currentRound.taken_at); + rows.push( + , + ); + } + return rows; + }; + + if (!dailyRoundsList?.length) { + return; + } + const sortedData: DailyRoundsModel[] = dailyRoundsList.sort( + compareByDateString("taken_at"), + ); + + return ( +
+ + + + + + + + + + + {VentilatorTableBody(sortedData)} +
+ {t("ventilator_log")} +
{t("start_datetime")}{t("end_datetime")}{t("ventilator_modality")} + {`${t("ventilator_mode")} / ${t("ventilator_oxygen_modality")}`} +
+
+ ); +} diff --git a/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx b/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx index 82eb7f3da4d..6183f19368d 100644 --- a/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx +++ b/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx @@ -4,7 +4,7 @@ import { formatDateTime } from "@/Utils/utils"; export default function BinaryChronologicalChart(props: { data: { - value: boolean; + value: boolean | undefined; timestamp: string; notes?: string; }[]; diff --git a/src/components/Facility/Consultations/components/LinePlot.tsx b/src/components/Facility/Consultations/components/LinePlot.tsx index 0829527faae..e9f1adf731f 100644 --- a/src/components/Facility/Consultations/components/LinePlot.tsx +++ b/src/components/Facility/Consultations/components/LinePlot.tsx @@ -12,12 +12,21 @@ export const LinePlot = (props: any) => { const { title, name, - xData, - yData, low = null, high = null, defaultSpace, + verticalMarkerData = null, } = props; + let { xData, yData } = props; + const yDatacount = yData.filter( + (item: number | null): item is number => + item !== null && !Number.isNaN(item), + ).length; + if (yDatacount === 0) { + yData = []; + xData = []; + } + let generalOptions: any = { grid: { top: "40px", @@ -106,6 +115,25 @@ export const LinePlot = (props: any) => { ], }; + if (verticalMarkerData && yDatacount > 0) { + let series = generalOptions.series[0]; + series = { + ...series, + markLine: { + silent: true, + data: verticalMarkerData, + symbol: "none", + lineStyle: { + color: "#000000", + }, + }, + }; + generalOptions = { + ...generalOptions, + series, + }; + } + if (props.type && props.type === "WAVEFORM") { generalOptions = { ...generalOptions, diff --git a/src/components/Facility/Consultations/components/ReactEcharts.tsx b/src/components/Facility/Consultations/components/ReactEcharts.tsx index 326bd23661a..4215b34fa4d 100644 --- a/src/components/Facility/Consultations/components/ReactEcharts.tsx +++ b/src/components/Facility/Consultations/components/ReactEcharts.tsx @@ -5,6 +5,7 @@ import { DataZoomComponent, GridComponent, LegendComponent, + MarkLineComponent, TitleComponent, ToolboxComponent, TooltipComponent, @@ -27,6 +28,7 @@ echarts.use([ TooltipComponent, VisualMapComponent, VisualMapPiecewiseComponent, + MarkLineComponent, ]); interface ReactEchartsProps extends EChartsReactProps { From 45ae794dc0ad99285dd66a4d6cb778652d3013fb Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:26:01 +0530 Subject: [PATCH 07/99] Fix Conditional Rendering of TextAreaFormField for Sample Test Type (#8960) --- public/locale/en.json | 23 ++- src/components/Patient/SampleDetails.tsx | 170 ++++++++++++++-------- src/components/Patient/SampleTest.tsx | 2 +- src/components/Patient/SampleTestCard.tsx | 5 +- 4 files changed, 130 insertions(+), 70 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index ef591130213..ce1d79aa3dd 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -314,6 +314,7 @@ "are_you_still_watching": "Are you still watching?", "are_you_sure_want_to_delete": "Are you sure you want to delete {{name}}?", "are_you_sure_want_to_delete_this_record": "Are you sure want to delete this record?", + "ari": "ARI - Acute Respiratory illness", "asset_class": "Asset Class", "asset_location": "Asset Location", "asset_name": "Asset Name", @@ -324,6 +325,7 @@ "assigned_facility": "Facility assigned", "assigned_to": "Assigned to", "async_operation_warning": "This operation may take some time. Please check back later.", + "atypical_presentation_details": "Atypical presentation details", "audio__allow_permission": "Please allow microphone permission in site settings", "audio__allow_permission_button": "Click here to know how to allow", "audio__allow_permission_helper": "You might have denied microphone access in the past.", @@ -490,6 +492,8 @@ "contact_person_at_the_facility": "Contact person at the current facility", "contact_person_number": "Contact person number", "contact_phone": "Contact Person Number", + "contact_with_confirmed_carrier": "Contact with confirmed carrier", + "contact_with_suspected_carrier": "Contact with suspected carrier", "contact_your_admin_to_add_skills": "Contact your admin to add skills", "continue": "Continue", "continue_watching": "Continue watching", @@ -554,6 +558,7 @@ "diagnosis_at_discharge": "Diagnosis at Discharge", "diastolic": "Diastolic", "differential": "Differential", + "differential_diagnosis": "Differential diagnosis", "discard": "Discard", "discharge": "Discharge", "discharge_from_care": "Discharge from CARE", @@ -571,7 +576,9 @@ "district": "District", "district_program_management_supporting_unit": "District Program Management Supporting Unit", "doctor_s_medical_council_registration": "Doctor's Medical Council Registration", + "doctors_name": "Doctor's Name", "domestic_healthcare_support": "Domestic healthcare support", + "domestic_international_travel": "Domestic/international Travel (within last 28 days)", "done": "Done", "dosage": "Dosage", "down": "Down", @@ -639,6 +646,7 @@ "error_while_deleting_record": "Error while deleting record", "escape": "Escape", "estimated_contact_date": "Estimated contact date", + "etiology_identified": "Etiology identified", "events": "Events", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", @@ -652,6 +660,7 @@ "facility_search_placeholder": "Search by Facility / District Name", "facility_type": "Facility Type", "failed_to_link_abha_number": "Failed to link ABHA Number. Please try again later.", + "fast_track_testing_reason": "Fast track testing reason", "features": "Features", "feed_configurations": "Feed Configurations", "feed_is_currently_not_live": "Feed is currently not live", @@ -697,6 +706,7 @@ "goal": "Our goal is to continuously improve the quality and accessibility of public healthcare services using digital tools.", "granted_on": "Granted On", "has_domestic_healthcare_support": "Has domestic healthcare support?", + "has_sari": "Has SARI (Severe Acute Respiratory illness)?", "health_facility__config_registration_error": "Health ID registration failed", "health_facility__config_update_error": "Health Facility config update failed", "health_facility__config_update_success": "Health Facility config updated successfully", @@ -730,6 +740,7 @@ "hubs": "Hub Facilities", "i_declare": "I hereby declare that:", "icd11_as_recommended": "As per ICD-11 recommended by WHO", + "icmr_specimen_referral_form": "ICMR Specimen Referral Form", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", "inconsistent_dosage_units_error": "Dosage units must be same", "indian_mobile": "Indian Mobile", @@ -764,15 +775,18 @@ "investigations_suggested": "Investigations Suggested", "is": "Is", "is_antenatal": "Is Antenatal", + "is_atypical_presentation": "Is Atypical presentation", "is_declared_positive": "Whether declared positive", "is_emergency": "Is emergency", "is_emergency_case": "Is emergency case", "is_it_upshift": "is it upshift", "is_this_an_emergency": "Is this an emergency?", "is_this_an_upshift": "Is this an upshift?", + "is_unusual_course": "Is unusual course", "is_up_shift": "Is up shift", "is_upshift_case": "Is upshift case", "is_vaccinated": "Whether vaccinated", + "label": "Label", "landline": "Indian landline", "language_selection": "Language Selection", "last_administered": "Last administered", @@ -902,7 +916,7 @@ "notice_board": "Notice Board", "notification_permission_denied": "Notification permission denied", "notification_permission_granted": "Notification permission granted", - "number_of_aged_dependents_above_60": "Number Of Aged Dependents (Above 60)", + "number_of_aged_dependents": "Number of Aged Dependents (Above 60)", "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", "number_of_chronic_diseased_dependents": "Number Of Chronic Diseased Dependents", @@ -936,6 +950,7 @@ "password_reset_failure": "Password Reset Failed", "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", + "patient": "Patient", "patient_address": "Patient Address", "patient_body": "Patient Body", "patient_category": "Patient Category", @@ -1077,6 +1092,7 @@ "result": "Result", "result_date": "Result Date", "result_details": "Result details", + "result_on": "Result on", "resume": "Resume", "retake": "Retake", "return_to_care": "Return to CARE", @@ -1090,7 +1106,11 @@ "sample_collection_date": "Sample Collection Date", "sample_format": "Sample Format", "sample_test": "Sample Test", + "sample_test_details": "Sample Test Details", + "sample_test_history": "Sample Test History", "sample_type": "Sample Type", + "sample_type_description": "Sample Type Description", + "sari": "SARI - Severe Acute Respiratory illness", "save": "Save", "save_and_continue": "Save and Continue", "save_investigation": "Save Investigation", @@ -1180,6 +1200,7 @@ "tachycardia": "Tachycardia", "target_dosage": "Target Dosage", "test_type": "Type of test done", + "tested_on": "Tested on", "third_party_software_licenses": "Third Party Software Licenses", "titrate_dosage": "Titrate Dosage", "to_be_conducted": "To be conducted", diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index c556177ec13..c16ef1c0e86 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -1,5 +1,6 @@ import { camelCase, capitalize, startCase } from "lodash-es"; import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; import Card from "@/CAREUI/display/Card"; @@ -17,6 +18,7 @@ import useQuery from "@/Utils/request/useQuery"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { + const { t } = useTranslation(); const { loading: isLoading, data: sampleDetails } = useQuery( routes.getTestSample, { @@ -33,9 +35,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { const yesOrNoBadge = (param: any) => param ? ( - Yes + {t("yes")} ) : ( - No + {t("no")} ); const showPatientCard = (patientData: any) => { @@ -51,20 +53,24 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Name: + + {t("name")}:{" "} + {patientData?.name}
{patientData?.is_medical_worker && (
- Medical Worker:{" "} + {t("medical_worker")}:{" "} + + + {t("yes")} - Yes
)}
- Disease Status:{" "} + {t("disease_status")}:{" "} {patientData?.disease_status} @@ -72,16 +78,20 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- SRF ID: + + {t("srf_id")}:{" "} + {(patientData?.srf_id && patientData?.srf_id) || "-"}
- Test Type: + + {t("test_type")}:{" "} + {(patientData?.test_type && testType) || "-"}
- Date of Test:{" "} + {t("date_of_test")}:{" "} {(patientData?.date_of_test && formatDateTime(patientData?.date_of_test)) || @@ -89,35 +99,43 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Facility: + + {t("facility")}:{" "} + {patientData?.facility_object?.name || "-"}
{patientData?.date_of_birth ? (
- Date of birth:{" "} + {t("date_of_birth")}:{" "} {patientData?.date_of_birth}
) : (
- Age: + + {t("age")}:{" "} + {formatPatientAge(patientData)}
)}
- Gender: + + {t("gender")}:{" "} + {patientGender}
- Phone: + + {t("phone")}:{" "} + {patientData?.phone_number || "-"}
- Nationality:{" "} + {t("nationality")}:{" "} {patientData?.nationality || "-"}
@@ -125,14 +143,14 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Blood Group:{" "} + {t("blood_group")}:{" "} {patientData?.blood_group || "-"}
{patientData?.nationality !== "India" && (
- Passport Number:{" "} + {t("passport_number")}:{" "} {patientData?.passport_no || "-"}
@@ -140,56 +158,60 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.nationality === "India" && ( <>
- State: + + {t("state")}:{" "} + {patientData?.state_object?.name}
- District:{" "} + {t("district")}:{" "} {patientData?.district_object?.name || "-"}
- Local Body:{" "} + {t("local_body")}:{" "} {patientData?.local_body_object?.name || "-"}
)}
- Address: + + {t("address")}:{" "} + {patientData?.address || "-"}
- Contact with confirmed carrier:{" "} + {t("contact_with_confirmed_carrier")}:{" "} {yesOrNoBadge(patientData?.contact_with_confirmed_carrier)}
- Contact with suspected carrier:{" "} + {t("contact_with_suspected_carrier")}:{" "} {yesOrNoBadge(patientData?.contact_with_suspected_carrier)}
{patientData?.estimated_contact_date && (
- Estimated contact date:{" "} + {t("estimated_contact_date")}:{" "} {formatDateTime(patientData?.estimated_contact_date)}
)}
- Has SARI (Severe Acute Respiratory illness)?:{" "} + {t("has_sari")}:{" "} {yesOrNoBadge(patientData?.has_SARI)}
- Domestic/international Travel (within last 28 days):{" "} + {t("domestic_international_travel")}:{" "} {yesOrNoBadge(patientData?.past_travel)}
@@ -197,7 +219,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { !!patientData?.countries_travelled.length && (
- Countries travelled:{" "} + {t("countries_travelled")}:{" "} {patientData?.countries_travelled.join(", ")}
@@ -205,7 +227,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.ongoing_medication && (
- Ongoing Medications{" "} + {t("ongoing_medications")}{" "} {patientData?.ongoing_medication}
@@ -213,7 +235,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.allergies && (
- Allergies:{" "} + {t("allergies")}:{" "} {patientData?.allergies}
@@ -221,7 +243,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {!!patientData?.number_of_aged_dependents && (
- Number Of Aged Dependents (Above 60):{" "} + {t("number_of_aged_dependents")}:{" "} {patientData?.number_of_aged_dependents}
@@ -229,7 +251,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {!!patientData?.number_of_chronic_diseased_dependents && (
- Number Of Chronic Diseased Dependents:{" "} + {t("number_of_chronic_diseased_dependents")}:{" "} {patientData?.number_of_chronic_diseased_dependents}
@@ -245,19 +267,25 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status: {" "} + + {t("status")}:{" "} + {" "} {startCase(camelCase(flow.status))}
- Label:{" "} + {t("label")}:{" "} {capitalize(flow.notes)}
- Created On :{" "} + + {t("created_on")}: + {" "} {flow.created_date ? formatDateTime(flow.created_date) : "-"}
- Modified on:{" "} + + {t("modified_on")}: + {" "} {flow.modified_date ? formatDateTime(flow.modified_date) : "-"}
@@ -271,7 +299,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { return ( { - ICMR Specimen Referral Form + {t("icmr_specimen_referral_form")}
) @@ -289,34 +317,42 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status:{" "} + {t("status")}:{" "} {sampleDetails?.status}
- Result:{" "} + {t("result")}:{" "} {sampleDetails?.result}
- Patient: + + {t("patient")}:{" "} + {sampleDetails?.patient_name}
{sampleDetails?.facility_object && (
- Facility: + + {t("facility")}:{" "} + {sampleDetails?.facility_object.name}
)}
- Tested on: + + {t("tested_on")}:{" "} + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"}
- Result on: + + {t("result_on")}:{" "} + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"} @@ -324,7 +360,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.fast_track && (
- Fast track testing reason:{" "} + {t("fast_track_testing_reason")}:{" "} {sampleDetails.fast_track}
@@ -332,21 +368,23 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.doctor_name && (
- Doctor's Name:{" "} + {t("doctors_name")}:{" "} {startCase(camelCase(sampleDetails.doctor_name))}
)} {sampleDetails?.diagnosis && (
- Diagnosis: + + {t("diagnosis")}:{" "} + {sampleDetails.diagnosis}
)} {sampleDetails?.diff_diagnosis && (
- Differential diagnosis:{" "} + {t("differential_diagnosis")}:{" "} {sampleDetails?.diff_diagnosis}
@@ -354,52 +392,48 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.etiology_identified && (
- Etiology identified:{" "} + {t("etiology_identified")}:{" "} {sampleDetails.etiology_identified}
)}
- Is Atypical presentation{" "} + {t("is_atypical_presentation")}{" "} {yesOrNoBadge(sampleDetails?.is_atypical_presentation)}
- Is unusual course{" "} + {t("is_unusual_course")}{" "} {yesOrNoBadge(sampleDetails?.is_unusual_course)}
{sampleDetails?.atypical_presentation && (
- Atypical presentation details:{" "} + {t("atypical_presentation_details")}:{" "} {sampleDetails.atypical_presentation}
)}
- - SARI - Severe Acute Respiratory illness{" "} - + {t("sari")} {yesOrNoBadge(sampleDetails?.has_sari)}
- - ARI - Acute Respiratory illness{" "} - + {t("ari")} {yesOrNoBadge(sampleDetails?.has_ari)}
- Contact with confirmed carrier{" "} + {t("contact_with_confirmed_carrier")}{" "} {yesOrNoBadge(sampleDetails?.patient_has_confirmed_contact)}
- Contact with suspected carrier{" "} + {t("contact_with_suspected_carrier")}{" "} {yesOrNoBadge(sampleDetails?.patient_has_suspected_contact)}
@@ -407,29 +441,37 @@ export const SampleDetails = ({ id }: DetailRoute) => { sampleDetails.patient_travel_history.length !== 0 && (
- Countries travelled:{" "} + {t("countries_travelled")}:{" "} {sampleDetails.patient_travel_history}
)} {sampleDetails?.sample_type && ( -
+
- Sample Type:{" "} + {t("sample_type")}:{" "} {startCase(camelCase(sampleDetails.sample_type))}
)} + {sampleDetails?.sample_type === "OTHER TYPE" && ( +
+ + {t("sample_type_description")}:{" "} + + {sampleDetails?.sample_type_other} +
+ )}
-

Details of patient

+

{t("details_of_patient")}

{showPatientCard(sampleDetails?.patient_object)}
-

Sample Test History

+

{t("sample_test_history")}

{sampleDetails?.flow && sampleDetails.flow.map((flow: FlowModel) => renderFlow(flow))}
diff --git a/src/components/Patient/SampleTest.tsx b/src/components/Patient/SampleTest.tsx index afddb81b654..0714de28492 100644 --- a/src/components/Patient/SampleTest.tsx +++ b/src/components/Patient/SampleTest.tsx @@ -218,7 +218,7 @@ export const SampleTest = ({ facilityId, patientId }: any) => { optionValue={(option) => option.id} /> - {state.form.sample_type === "OTHER TYPE" && ( + {state.form.sample_type === "9" && ( { Sample Type{" "}
- {(itemData.sample_type !== "OTHER TYPE" - ? itemData.sample_type - : itemData.sample_type_other - )?.toLowerCase()} + {itemData.sample_type?.toLowerCase()}
From 5502f1691adcb29da15a1a2ea05e178adf117ef4 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Mon, 11 Nov 2024 11:01:14 +0530 Subject: [PATCH 08/99] fixes auto needs testing label (#9025) --- .github/workflows/auto-testing-label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-testing-label.yml b/.github/workflows/auto-testing-label.yml index 6c6fc1002a0..98cfd46dab3 100644 --- a/.github/workflows/auto-testing-label.yml +++ b/.github/workflows/auto-testing-label.yml @@ -38,7 +38,7 @@ jobs: } if (isChangesRequired) { - await github.issues.createComment({ + await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, From 68bfde477ed7f50af2f87e27ff2edb2a4b38cf6b Mon Sep 17 00:00:00 2001 From: Shaurya Gupta Date: Mon, 11 Nov 2024 11:52:47 +0530 Subject: [PATCH 09/99] Update tooltip position and rotation in Discussion Notes panel (#8938) --- .../Facility/PatientNotesSlideover.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/Facility/PatientNotesSlideover.tsx b/src/components/Facility/PatientNotesSlideover.tsx index 05deef36b91..89d38a5f168 100644 --- a/src/components/Facility/PatientNotesSlideover.tsx +++ b/src/components/Facility/PatientNotesSlideover.tsx @@ -161,15 +161,19 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { id="expand_doctor_notes" className={classNames( "tooltip flex h-8 w-8 cursor-pointer items-center justify-center rounded bg-primary-800 text-secondary-100 text-opacity-70 hover:bg-primary-700 hover:text-opacity-100", - show && "rotate-180", )} onClick={() => setShow(!show)} > - + {t("minimize")}
@@ -181,7 +185,12 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { icon="l-times" className="tooltip text-lg transition-all delay-150 duration-300 ease-out" /> - + {t("close")} From cc9d0c977b3ebc03b4a86bb385bc03e299834f32 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:46:38 +0530 Subject: [PATCH 10/99] Add Cypress Test Suite for Facility Notice Board Functionality Verification (#9045) --- .../e2e/facility_spec/FacilityHomepage.cy.ts | 53 ++++++++++++++++- cypress/pageobject/Facility/FacilityHome.ts | 3 +- cypress/pageobject/Facility/FacilityNotify.ts | 58 +++++++++++++++++++ cypress/pageobject/Login/LoginPage.ts | 4 ++ cypress/pageobject/Users/ManageUserPage.ts | 8 +++ src/components/Common/Sidebar/SidebarItem.tsx | 2 + src/components/Facility/FacilityCard.tsx | 5 +- src/components/Notifications/NoticeBoard.tsx | 4 +- .../Notifications/NotificationsList.tsx | 5 +- 9 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 cypress/pageobject/Facility/FacilityNotify.ts diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index bc84aea4882..7db3b308a53 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -2,6 +2,7 @@ import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; +import FacilityNotify from "../../pageobject/Facility/FacilityNotify"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; import { UserPage } from "../../pageobject/Users/UserSearch"; @@ -9,6 +10,7 @@ import { UserPage } from "../../pageobject/Users/UserSearch"; describe("Facility Homepage Function", () => { const loginPage = new LoginPage(); const facilityHome = new FacilityHome(); + const facilityNotify = new FacilityNotify(); const facilityPage = new FacilityPage(); const manageUserPage = new ManageUserPage(); const userPage = new UserPage(); @@ -22,6 +24,8 @@ describe("Facility Homepage Function", () => { const district = "Ernakulam"; const localBody = "Aikaranad"; const facilityType = "Private Hospital"; + const notificationErrorMsg = "Message cannot be empty"; + const notificationMessage = "Test Notification"; before(() => { loginPage.loginAsDistrictAdmin(); @@ -30,6 +34,7 @@ describe("Facility Homepage Function", () => { beforeEach(() => { cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); cy.awaitUrl("/facility"); }); @@ -41,9 +46,6 @@ describe("Facility Homepage Function", () => { facilityHome.clickViewCnsButton(); facilityHome.verifyCnsUrl(); facilityHome.navigateBack(); - // view notify button - facilityHome.clickFacilityNotifyButton(); - facilityHome.verifyAndCloseNotifyModal(); // view facility button facilityHome.clickViewFacilityDetails(); facilityPage.getFacilityName().should("be.visible"); @@ -134,6 +136,51 @@ describe("Facility Homepage Function", () => { facilityHome.verifyLiveMonitorUrl(); }); + it("Verify Notice Board Functionality", () => { + // search facility and verify it's loaded or not + manageUserPage.interceptFacilitySearchReq(); + manageUserPage.typeFacilitySearch(facilityName); + manageUserPage.verifyFacilitySearchReq(); + // verify facility name and card reflection + facilityNotify.verifyUrlContains("Dummy+Facility+40"); + facilityPage.verifyFacilityBadgeContent(facilityName); + manageUserPage.assertFacilityInCard(facilityName); + // send notification to a facility + facilityHome.clickFacilityNotifyButton(); + facilityNotify.verifyFacilityName(facilityName); + facilityNotify.fillNotifyText(notificationMessage); + facilityNotify.interceptPostNotificationReq(); + cy.submitButton("Notify"); + facilityNotify.verifyPostNotificationReq(); + cy.verifyNotification("Facility Notified"); + cy.closeNotification(); + cy.wait(2000); + // Verify the frontend error on empty message + facilityHome.clickFacilityNotifyButton(); + facilityNotify.verifyFacilityName(facilityName); + cy.submitButton("Notify"); + facilityNotify.verifyErrorMessage(notificationErrorMsg); + // close pop-up and verify + facilityHome.verifyAndCloseNotifyModal(); + // signout as district admin and login as a Nurse + loginPage.ensureLoggedIn(); + loginPage.clickSignOutBtn(); + loginPage.loginManuallyAsNurse(); + // Verify Notice Board Reflection + facilityNotify.interceptGetNotificationReq("MESSAGE"); + facilityNotify.visitNoticeBoard(); + facilityNotify.verifyGetNotificationReq(); + facilityNotify.verifyFacilityNoticeBoardMessage(notificationMessage); + facilityNotify.interceptGetNotificationReq(); + // Verify Sidebar Notification Reflection + facilityNotify.openNotificationSlide(); + facilityNotify.verifyGetNotificationReq(); + cy.verifyContentPresence("#notification-slide-msg", [notificationMessage]); + facilityNotify.closeNotificationSlide(); + loginPage.ensureLoggedIn(); + loginPage.clickSignOutBtn(); + }); + afterEach(() => { cy.saveLocalStorage(); }); diff --git a/cypress/pageobject/Facility/FacilityHome.ts b/cypress/pageobject/Facility/FacilityHome.ts index dea7de0e7b6..30f51052370 100644 --- a/cypress/pageobject/Facility/FacilityHome.ts +++ b/cypress/pageobject/Facility/FacilityHome.ts @@ -35,7 +35,8 @@ class FacilityHome { } clickFacilityNotifyButton() { - cy.get("#facility-notify").first().click(); + cy.get("#facility-notify", { timeout: 10000 }).should("be.visible"); + cy.get("#facility-notify").focus().click(); } clickLiveMonitorButton() { diff --git a/cypress/pageobject/Facility/FacilityNotify.ts b/cypress/pageobject/Facility/FacilityNotify.ts new file mode 100644 index 00000000000..08c44b32e84 --- /dev/null +++ b/cypress/pageobject/Facility/FacilityNotify.ts @@ -0,0 +1,58 @@ +export default class FacilityNotify { + verifyFacilityName(facilityName: string) { + cy.verifyContentPresence("#notify-facility-name", [facilityName]); + } + + verifyErrorMessage(errorMessage: string) { + cy.verifyContentPresence(".error-text", [errorMessage]); + } + + fillNotifyText(message: string) { + cy.get("#NotifyModalMessageInput").scrollIntoView(); + cy.get("#NotifyModalMessageInput").click().type(message); + } + + verifyFacilityNoticeBoardMessage(message: string) { + cy.get("#notification-message", { timeout: 10000 }).should("be.visible"); + cy.verifyContentPresence("#notification-message", [message]); + } + + openNotificationSlide() { + cy.get("#notification-slide-btn").should("be.visible").click(); + } + + closeNotificationSlide() { + cy.get("#close-slide-over").should("be.visible").click(); + } + + visitNoticeBoard() { + cy.get("a[href='/notice_board']").should("be.visible").click(); + } + + visitNotificationSideBar() { + cy.get("#notification-slide-btn").should("be.visible").click(); + } + + verifyUrlContains(substring: string) { + cy.url().should("include", substring); + } + + interceptPostNotificationReq() { + cy.intercept("POST", "**/api/v1/notification/notify").as("notifyFacility"); + } + + verifyPostNotificationReq() { + cy.wait("@notifyFacility").its("response.statusCode").should("eq", 204); + } + + interceptGetNotificationReq(event: string = "") { + cy.intercept( + "GET", + `**/api/v1/notification/?offset=0&event=${event}&*=SYSTEM`, + ).as("getNotifications"); + } + + verifyGetNotificationReq() { + cy.wait("@getNotifications").its("response.statusCode").should("eq", 200); + } +} diff --git a/cypress/pageobject/Login/LoginPage.ts b/cypress/pageobject/Login/LoginPage.ts index cd5230a7772..38b8aeee2af 100644 --- a/cypress/pageobject/Login/LoginPage.ts +++ b/cypress/pageobject/Login/LoginPage.ts @@ -34,6 +34,10 @@ class LoginPage { cy.get("#sign-out-button").scrollIntoView(); cy.get("#sign-out-button").contains("Sign Out").should("exist"); } + + clickSignOutBtn(): void { + cy.verifyAndClickElement("#sign-out-button", "Sign Out"); + } } export default LoginPage; diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index 470862693a8..a3a6e72fbc3 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -99,6 +99,14 @@ export class ManageUserPage { cy.get("#search").click().type(facilityName); } + interceptFacilitySearchReq() { + cy.intercept("GET", "**/api/v1/facility/**").as("searchFacility"); + } + + verifyFacilitySearchReq() { + cy.wait("@searchFacility").its("response.statusCode").should("eq", 200); + } + assertFacilityInCard(facilityName: string) { cy.get("#facility-name-card").should("contain", facilityName); } diff --git a/src/components/Common/Sidebar/SidebarItem.tsx b/src/components/Common/Sidebar/SidebarItem.tsx index 31f64754ffd..7262c6a103b 100644 --- a/src/components/Common/Sidebar/SidebarItem.tsx +++ b/src/components/Common/Sidebar/SidebarItem.tsx @@ -9,6 +9,7 @@ import useAppHistory from "@/hooks/useAppHistory"; export type SidebarIcon = React.ReactNode; type SidebarItemProps = { + id?: string; ref?: React.Ref; text: string; icon: SidebarIcon; @@ -31,6 +32,7 @@ const SidebarItemBase = forwardRef( return ( + Notify: {facility.name} } diff --git a/src/components/Notifications/NoticeBoard.tsx b/src/components/Notifications/NoticeBoard.tsx index e4472107c8f..e1f00075000 100644 --- a/src/components/Notifications/NoticeBoard.tsx +++ b/src/components/Notifications/NoticeBoard.tsx @@ -26,7 +26,9 @@ export const NoticeBoard = () => { className="overflow-hidden rounded shadow-md" >
-
{item.message}
+
+ {item.message} +
{formatName(item.caused_by)} -{" "} diff --git a/src/components/Notifications/NotificationsList.tsx b/src/components/Notifications/NotificationsList.tsx index 5d88cc8d603..0808877a444 100644 --- a/src/components/Notifications/NotificationsList.tsx +++ b/src/components/Notifications/NotificationsList.tsx @@ -115,7 +115,9 @@ const NotificationTile = ({ />
-
{result.message}
+
+ {result.message} +
{formatDateTime(result.created_date)} @@ -474,6 +476,7 @@ export default function NotificationsList({ <> setOpen(!open)} icon={} badgeCount={unreadCount} From 5d95dc68bbbe3868f1338fbb3e3b3ff3f127ed81 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:46:03 +0530 Subject: [PATCH 11/99] New Cypress Test | Create Partial Investigation Log Update (#9034) --- .../e2e/patient_spec/PatientHomepage.cy.ts | 1 - .../patient_spec/PatientInvestigation.cy.ts | 40 +++++++++++++++++++ .../Patient/PatientInvestigation.ts | 10 ++++- .../ConsultationInvestigationsTab.tsx | 1 + .../Investigations/ViewInvestigations.tsx | 2 + 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 cypress/e2e/patient_spec/PatientInvestigation.cy.ts diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts index 32d869b8f50..53fb8732712 100644 --- a/cypress/e2e/patient_spec/PatientHomepage.cy.ts +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -43,7 +43,6 @@ describe("Patient Homepage present functionalities", () => { patientHome.typePatientAdmitedBeforeDate(patientFromDate); patientHome.typePatientAdmitedAfterDate(patientToDate); patientHome.clickPatientFilterApply(); - patientHome.verifyTotalPatientCount("1"); // verify the badge and clear the count patientHome.verifyPatientCreatedBeforeDate(patientToDateBadge); patientHome.verifyPatientCreatedAfterDate(patientFromDateBadge); diff --git a/cypress/e2e/patient_spec/PatientInvestigation.cy.ts b/cypress/e2e/patient_spec/PatientInvestigation.cy.ts new file mode 100644 index 00000000000..380adf2c37e --- /dev/null +++ b/cypress/e2e/patient_spec/PatientInvestigation.cy.ts @@ -0,0 +1,40 @@ +import { PatientPage } from "pageobject/Patient/PatientCreation"; +import PatientInvestigation from "pageobject/Patient/PatientInvestigation"; + +import LoginPage from "../../pageobject/Login/LoginPage"; + +describe("Patient Investigation Creation from Patient consultation page", () => { + const loginPage = new LoginPage(); + const patientPage = new PatientPage(); + const patientInvestigation = new PatientInvestigation(); + const patientName = "Dummy Patient 12"; + + before(() => { + loginPage.loginAsDistrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Create a investigation for a patient and verify its reflection", () => { + patientPage.visitPatient(patientName); + patientInvestigation.clickInvestigationTab(); + patientInvestigation.clickLogLabResults(); + patientInvestigation.selectInvestigationOption([ + "Haematology", + "Urine Test", + ]); + cy.submitButton("Save Investigation"); + cy.verifyNotification("Please Enter at least one value"); + cy.closeNotification(); + // Temporary workflow for investigation since we dont have dummy data and moving away from existing module + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Patient/PatientInvestigation.ts b/cypress/pageobject/Patient/PatientInvestigation.ts index 8f73cf908bc..a9f8f38435a 100644 --- a/cypress/pageobject/Patient/PatientInvestigation.ts +++ b/cypress/pageobject/Patient/PatientInvestigation.ts @@ -1,11 +1,9 @@ class PatientInvestigation { clickAddInvestigation() { - cy.get("#investigation").scrollIntoView(); cy.verifyAndClickElement("#investigation", "Add Investigation"); } clickInvestigationTab() { - cy.get("#consultation_tab_nav").scrollIntoView(); cy.verifyAndClickElement("#consultation_tab_nav", "Investigations"); } @@ -19,6 +17,14 @@ class PatientInvestigation { cy.get("#investigation-checkbox").click(); } + selectInvestigationOption(options: string[]) { + cy.clickAndMultiSelectOption("#investigations", options); + } + + clickLogLabResults() { + cy.verifyAndClickElement("#log-lab-results", "Log Lab Results"); + } + selectInvestigationFrequency(frequency: string) { cy.get("#investigation-frequency").click(); cy.contains("button", frequency).should("be.visible").click(); diff --git a/src/components/Facility/ConsultationDetails/ConsultationInvestigationsTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationInvestigationsTab.tsx index ed1157f2c95..bbc17d925a9 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationInvestigationsTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationInvestigationsTab.tsx @@ -17,6 +17,7 @@ export const ConsultationInvestigationsTab = (props: ConsultationTabProps) => {
diff --git a/src/components/Facility/Investigations/ViewInvestigations.tsx b/src/components/Facility/Investigations/ViewInvestigations.tsx index 162fd6d87e0..3bd80b0bb28 100644 --- a/src/components/Facility/Investigations/ViewInvestigations.tsx +++ b/src/components/Facility/Investigations/ViewInvestigations.tsx @@ -62,6 +62,7 @@ export default function ViewInvestigations(props: {
navigate( `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/investigation/${investigationSession.session_external_id}`, @@ -73,6 +74,7 @@ export default function ViewInvestigations(props: { {t("view")} navigate( `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/investigation/${investigationSession.session_external_id}/print`, From 693184592c15d3c5f287213fde120c412faefe32 Mon Sep 17 00:00:00 2001 From: Srayash <146334722+Srayash@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:48:08 +0530 Subject: [PATCH 12/99] "fix icon overlap in edit consultation page." #9077 (#9078) --- src/components/Facility/ConsultationForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Facility/ConsultationForm.tsx b/src/components/Facility/ConsultationForm.tsx index 34cc5973832..d6d94593049 100644 --- a/src/components/Facility/ConsultationForm.tsx +++ b/src/components/Facility/ConsultationForm.tsx @@ -936,7 +936,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
{ From fc4f94debbc0b41394ca01087a90bbfc35b01c37 Mon Sep 17 00:00:00 2001 From: Nithish Kumar Siliveru Date: Mon, 11 Nov 2024 17:11:59 +0530 Subject: [PATCH 13/99] Fixed search icon and removed delete option (#9059) --- src/Utils/request/api.tsx | 12 ------ src/components/Form/SearchInput.tsx | 2 +- src/components/Resource/ResourceDetails.tsx | 46 --------------------- src/components/Shifting/ShiftDetails.tsx | 43 ------------------- 4 files changed, 1 insertion(+), 102 deletions(-) diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 441b9a3d8c8..e1777364b3f 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -1065,11 +1065,6 @@ const routes = { TBody: Type(), TRes: Type(), }, - deleteShiftRecord: { - path: "/api/v1/shift/{id}/", - method: "DELETE", - TRes: Type<{ detail: string }>(), - }, listShiftRequests: { path: "/api/v1/shift/", method: "GET", @@ -1241,13 +1236,6 @@ const routes = { TRes: Type(), TBody: Type>(), }, - deleteResourceRecord: { - path: "/api/v1/resource/{id}/", - method: "DELETE", - TRes: Type<{ - detail?: string; - }>(), - }, listResourceRequests: { path: "/api/v1/resource/", method: "GET", diff --git a/src/components/Form/SearchInput.tsx b/src/components/Form/SearchInput.tsx index bbf59305a0a..69334c0f4db 100644 --- a/src/components/Form/SearchInput.tsx +++ b/src/components/Form/SearchInput.tsx @@ -87,7 +87,7 @@ const SearchInput = ({ className={className} leading={ props.leading || ( - + ) } trailing={ diff --git a/src/components/Resource/ResourceDetails.tsx b/src/components/Resource/ResourceDetails.tsx index 2495c64433d..d77bdcc3fea 100644 --- a/src/components/Resource/ResourceDetails.tsx +++ b/src/components/Resource/ResourceDetails.tsx @@ -4,21 +4,16 @@ import { useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; 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 CommentSection from "@/components/Resource/ResourceCommentSection"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; import { classNames, formatDateTime, formatName } from "@/Utils/utils"; export default function ResourceDetails(props: { id: string }) { const [isPrintMode, setIsPrintMode] = useState(false); - const [openDeleteResourceDialog, setOpenDeleteResourceDialog] = - useState(false); const { data, loading } = useQuery(routes.getResourceDetails, { pathParams: { id: props.id }, onResponse: ({ res, data }) => { @@ -27,25 +22,6 @@ export default function ResourceDetails(props: { id: string }) { } }, }); - - const handleResourceDelete = async () => { - setOpenDeleteResourceDialog(true); - const { res, data } = await request(routes.deleteResourceRecord, { - pathParams: { id: props.id }, - }); - if (res?.status === 204) { - Notification.Success({ - msg: "Resource record has been deleted successfully.", - }); - } else { - Notification.Error({ - msg: "Error while deleting Resource: " + (data?.detail || ""), - }); - } - - navigate("/resource"); - }; - const showFacilityCard = (facilityData: any) => { return (
@@ -329,28 +305,6 @@ export default function ResourceDetails(props: { id: string }) {
{data.reason || "--"}
- -
-
- setOpenDeleteResourceDialog(true)} - > - Delete Record - - - setOpenDeleteResourceDialog(false)} - onConfirm={handleResourceDelete} - /> -
-

Audit Log

diff --git a/src/components/Shifting/ShiftDetails.tsx b/src/components/Shifting/ShiftDetails.tsx index d5b251eeb8d..8e85fda3d19 100644 --- a/src/components/Shifting/ShiftDetails.tsx +++ b/src/components/Shifting/ShiftDetails.tsx @@ -9,7 +9,6 @@ import RecordMeta from "@/CAREUI/display/RecordMeta"; import CareIcon from "@/CAREUI/icons/CareIcon"; 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 { ConsultationModel } from "@/components/Facility/models"; @@ -22,16 +21,13 @@ import { SHIFTING_CHOICES_WARTIME, } from "@/common/constants"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; import { formatDateTime, formatName, formatPatientAge } from "@/Utils/utils"; export default function ShiftDetails(props: { id: string }) { const [isPrintMode, setIsPrintMode] = useState(false); const [isCopied, setIsCopied] = useState(false); - const [openDeleteShiftDialog, setOpenDeleteShiftDialog] = useState(false); const { t } = useTranslation(); const shiftStatusOptions = careConfig.wartimeShifting @@ -41,26 +37,6 @@ export default function ShiftDetails(props: { id: string }) { const { data, loading } = useQuery(routes.getShiftDetails, { pathParams: { id: props.id }, }); - - const handleShiftDelete = async () => { - setOpenDeleteShiftDialog(true); - - const { res, data } = await request(routes.deleteShiftRecord, { - pathParams: { id: props.id }, - }); - if (res?.status == 204) { - Notification.Success({ - msg: t("shifting_deleted"), - }); - } else { - Notification.Error({ - msg: t("error_deleting_shifting") + (data?.detail || ""), - }); - } - - navigate("/shifting"); - }; - const showCopyToclipBoard = (data: any) => { return ( @@ -732,25 +708,6 @@ export default function ShiftDetails(props: { id: string }) { time={data?.modified_date} />
- -
-
- setOpenDeleteShiftDialog(true)} - > - {t("delete_record")} - - setOpenDeleteShiftDialog(false)} - onConfirm={handleShiftDelete} - /> -
-
From 8a9078d16b6dd5189c7919bb3fbd83ea7aa53a18 Mon Sep 17 00:00:00 2001 From: Sulochan Khadka <122200551+Sulochan-khadka@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:24:51 +0530 Subject: [PATCH 14/99] Dosage heading is misaligned in the prescription module #8645 (#8751) --- .../AdministrationTable.tsx | 22 +++++------ .../AdministrationTableRow.tsx | 39 ++++++++++++------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx index 86db7353832..b1eac32409d 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx +++ b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx @@ -31,17 +31,17 @@ export default function MedicineAdministrationTable({ -
- {t("medicine")} - -

Dosage &

-

- {prescriptions[0]?.dosage_type !== "PRN" - ? "Frequency" - : "Indicator"} -

-
-
+ {t("medicine")} + + + +

{t("dosage")} &

+

+ {prescriptions[0]?.dosage_type !== "PRN" + ? t("frequency") + : t("indicator")} +

+
diff --git a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx index d08506d7fbe..7544f405b08 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx +++ b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx @@ -59,6 +59,25 @@ export default function MedicineAdministrationTableRow({ key: `${prescription.last_administration?.administered_date}`, }, ); + const DosageFrequencyInfo = () => ( +
+
+ {prescription.dosage_type !== "TITRATED" ? ( +

{prescription.base_dosage}

+ ) : ( +

+ {prescription.base_dosage} - {prescription.target_dosage} +

+ )} + +

+ {prescription.dosage_type !== "PRN" + ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) + : prescription.indicator} +

+
+
+ ); return ( <> @@ -216,24 +235,14 @@ export default function MedicineAdministrationTableRow({ {prescription.medicine_object?.generic}
- -
- {prescription.dosage_type !== "TITRATED" ? ( -

{prescription.base_dosage}

- ) : ( -

- {prescription.base_dosage} - {prescription.target_dosage} -

- )} - -

- {prescription.dosage_type !== "PRN" - ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) - : prescription.indicator} -

+
+
+ + + From c6de776459bfd73ee91594309d5db9d8c1e843ec Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Mon, 11 Nov 2024 18:26:38 +0530 Subject: [PATCH 15/99] Fix min date for discharge (#9083) --- src/components/Facility/DischargeModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Facility/DischargeModal.tsx b/src/components/Facility/DischargeModal.tsx index 359e6437931..07229c9e17a 100644 --- a/src/components/Facility/DischargeModal.tsx +++ b/src/components/Facility/DischargeModal.tsx @@ -290,7 +290,9 @@ const DischargeModal = ({ setPreDischargeForm((form) => ({ ...form, ...updates })); }} required - min={new Date(consultationData?.encounter_date)} + min={dayjs(consultationData?.encounter_date) + .subtract(1, "day") + .toDate()} max={new Date()} error={ discharge_reason === From 25c8e0bd021ea5f7965743d8a3893918658ab200 Mon Sep 17 00:00:00 2001 From: Anvesh Nalimela <151531961+AnveshNalimela@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:10:58 +0530 Subject: [PATCH 16/99] Update for List View in Shifting from Gird LayOut to List Layout (#8882) --- .env | 2 +- public/locale/en.json | 1 + src/components/Shifting/ShiftingList.tsx | 267 ++++++++++++++++++----- 3 files changed, 216 insertions(+), 54 deletions(-) diff --git a/.env b/.env index b5db3d0088f..77e4641b8b2 100644 --- a/.env +++ b/.env @@ -14,4 +14,4 @@ ESLINT_NO_DEV_ERRORS=true CARE_CDN_URL="https://egov-s3-facility-10bedicu.s3.amazonaws.com https://egov-s3-patient-data-10bedicu.s3.amazonaws.com http://localhost:4566" REACT_ALLOWED_LOCALES="en,hi,ta,ml,mr,kn" -REACT_ENABLED_APPS="" +REACT_ENABLED_APPS="" \ No newline at end of file diff --git a/public/locale/en.json b/public/locale/en.json index ce1d79aa3dd..d686fa181a5 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -487,6 +487,7 @@ "consultation_not_filed_description": "Please file a consultation for this patient to continue.", "consultation_notes": "General Instructions (Advice)", "consultation_updates": "Consultation updates", + "contact_info": "Contact Info", "contact_number": "Contact Number", "contact_person": "Name of Contact Person at Facility", "contact_person_at_the_facility": "Contact person at the current facility", diff --git a/src/components/Shifting/ShiftingList.tsx b/src/components/Shifting/ShiftingList.tsx index 3af77809d7c..d4746ad434b 100644 --- a/src/components/Shifting/ShiftingList.tsx +++ b/src/components/Shifting/ShiftingList.tsx @@ -1,3 +1,4 @@ +import careConfig from "@careConfig"; import { navigate } from "raviger"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -13,15 +14,16 @@ import Page from "@/components/Common/Page"; import { ShiftingModel } from "@/components/Facility/models"; import SearchInput from "@/components/Form/SearchInput"; import BadgesList from "@/components/Shifting/ShiftingBadges"; -import ShiftingBlock from "@/components/Shifting/ShiftingBlock"; import { formatFilter } from "@/components/Shifting/ShiftingCommons"; import ListFilter from "@/components/Shifting/ShiftingFilters"; +import useAuthUser from "@/hooks/useAuthUser"; import useFilters from "@/hooks/useFilters"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; +import { formatDateTime } from "@/Utils/utils"; export default function ListView() { const { @@ -31,19 +33,31 @@ export default function ListView() { FilterBadges, advancedFilter, resultsPerPage, - } = useFilters({ cacheBlacklist: ["patient_name"], limit: 12 }); + } = useFilters({ cacheBlacklist: ["patient_name"] }); - const [modalFor, setModalFor] = useState(); + const [modalFor, setModalFor] = useState<{ + externalId: string | undefined; + loading: boolean; + }>({ + externalId: undefined, + loading: false, + }); + + const authUser = useAuthUser(); const { t } = useTranslation(); - const handleTransferComplete = async (shift?: ShiftingModel) => { - if (!shift) return; - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); + const handleTransferComplete = async (shift: ShiftingModel) => { + setModalFor({ ...modalFor, loading: true }); + try { + await request(routes.completeTransfer, { + pathParams: { externalId: shift.external_id }, + }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, + ); + } catch (error) { + setModalFor({ externalId: undefined, loading: false }); + } }; const { @@ -53,7 +67,6 @@ export default function ListView() { } = useQuery(routes.listShiftRequests, { query: formatFilter({ ...qParams, - limit: resultsPerPage, offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, }), }); @@ -62,42 +75,173 @@ export default function ListView() { if (loading) { return ; } - - if (!data || data.length === 0) { + if (data && !data.length) { return ( -
-
- {t("no_patients_to_show")} -
+
+ {t("no_patients_to_show")}
); } - return ( -
-
- {data.map((shift, i) => ( -
- setModalFor(shift)} - shift={shift} - /> + return data.map((shift: ShiftingModel) => ( +
+
+
+
+ {shift.patient_object.name} +
+
+ {shift.patient_object.age} +
+
+ +
+
+
+ +
+ {shift.patient_object.phone_number || ""} +
+
- ))} +
+
+ +
+ {shift.patient_object.address || "--"} +
+ +
+
+ +
+
+
+ +
{shift.status}
+ + +
+ {shift.emergency && ( + + {t("emergency")} + + )} +
+
+ +
+
+ +
+ {formatDateTime(shift.modified_date) || "--"} +
+ +
+
+ +
+
+ +
+ {shift.origin_facility_object?.name} +
+ + + {careConfig.wartimeShifting && ( +
+ +
+ {shift.shifting_approving_facility_object?.name} +
+ + )} + +
+ +
+ {shift.assigned_facility_external || + shift.assigned_facility_object?.name || + t("yet_to_be_decided")} +
+ +
+
+ navigate(`/shifting/${shift.external_id}`)} + variant="secondary" + border + className="w-full" + > + {t("all_details")} + + {shift.status === "COMPLETED" && shift.assigned_facility && ( +
+ + setModalFor({ + externalId: shift.external_id, + loading: false, + }) + } + > + {t("transfer_to_receiving_facility")} + + + setModalFor({ externalId: undefined, loading: false }) + } + onConfirm={() => handleTransferComplete(shift)} + /> +
+ )} +
- setModalFor(undefined)} - onConfirm={() => handleTransferComplete(modalFor)} - />
- ); + )); }; return ( @@ -118,7 +262,9 @@ export default function ListView() { breadcrumbs={false} options={ <> -
+
+ +
-
- {/* dummy div to align space as per board view */} -
-
+ +
+ advancedFilter.setShow(true)} + /> navigate("/shifting/board", { query: qParams })} @@ -137,10 +284,6 @@ export default function ListView() { {t("board_view")} - - advancedFilter.setShow(true)} - />
} @@ -151,9 +294,9 @@ export default function ListView() { ) : (
-
+
- - {showShiftingCardList(shiftData?.results || [])} +
+
+
+ {t("patients")} +
+
+ {t("contact_info")} +
+
+ {t("consent__status")} +
+
+ {t("facilities")} +
+
+ {t("LOG_UPDATE_FIELD_LABEL__action")} +
+
+
{showShiftingCardList(shiftData?.results || [])}
+
From b5a62ca9d8813f0f365e31d1a63ba317de4e93eb Mon Sep 17 00:00:00 2001 From: Aditya Jindal Date: Tue, 12 Nov 2024 05:43:10 +0530 Subject: [PATCH 17/99] Fix: Investigation record error in mobile mode (#9065) --- .../Facility/Investigations/InvestigationSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Facility/Investigations/InvestigationSuggestions.tsx b/src/components/Facility/Investigations/InvestigationSuggestions.tsx index 3ad3ea72675..0593a49e8a2 100644 --- a/src/components/Facility/Investigations/InvestigationSuggestions.tsx +++ b/src/components/Facility/Investigations/InvestigationSuggestions.tsx @@ -197,7 +197,7 @@ export default function ViewInvestigationSuggestions(props: {
- {Array.isArray(investigations) ? ( + {Array.isArray(investigations?.investigation) ? ( investigations.investigation?.map((investigation, index) => { let nextFurthestInvestigation: any = undefined; From c391c89c1accfb5221b93cf6a055a1238330b1ee Mon Sep 17 00:00:00 2001 From: Nithish Kumar Siliveru Date: Tue, 12 Nov 2024 05:44:14 +0530 Subject: [PATCH 18/99] fixes dropdown click events issue (#9047) --- .../Common/Sidebar/SidebarUserCard.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/Common/Sidebar/SidebarUserCard.tsx b/src/components/Common/Sidebar/SidebarUserCard.tsx index 58bd0e6be4c..27d2a64d9d9 100644 --- a/src/components/Common/Sidebar/SidebarUserCard.tsx +++ b/src/components/Common/Sidebar/SidebarUserCard.tsx @@ -36,7 +36,9 @@ const SidebarUserCard: React.FC = ({ shrinked }) => { + + + +
{t("profile")}
+
+ + +
{t("sign_out")}
+
+
); From 899196e70d108e587d72e348507ed2ff6aec7d02 Mon Sep 17 00:00:00 2001 From: Mahendar Chikolla <119734520+Mahendar0701@users.noreply.github.com> Date: Tue, 12 Nov 2024 05:45:03 +0530 Subject: [PATCH 19/99] Fix Notification Subscription Loading and Messaging Errors (#9038) --- public/locale/en.json | 4 + .../Notifications/NotificationsList.tsx | 125 ++++++++++-------- 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index d686fa181a5..1159c4ab0dc 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -915,6 +915,7 @@ "notes": "Notes", "notes_placeholder": "Type your Notes", "notice_board": "Notice Board", + "notification_cancelled": "Notification cancelled", "notification_permission_denied": "Notification permission denied", "notification_permission_granted": "Notification permission granted", "number_of_aged_dependents": "Number of Aged Dependents (Above 60)", @@ -1191,7 +1192,9 @@ "submitting": "Submitting", "subscribe": "Subscribe", "subscribe_on_this_device": "Subscribe on this device", + "subscribed_successfully": "Subscribed Successfully", "subscription_error": "Subscription Error", + "subscription_failed": "Subscription Failed", "suggested_investigations": "Suggested Investigations", "summary": "Summary", "support": "Support", @@ -1235,6 +1238,7 @@ "unlink_camera_and_bed": "Unlink this bed from this camera", "unsubscribe": "Unsubscribe", "unsubscribe_failed": "Unsubscribe failed.", + "unsubscribed_successfully": "Unsubscribed Successfully.", "unsupported_browser": "Unsupported Browser", "unsupported_browser_description": "Your browser ({{name}} version {{version}}) is not supported. Please update your browser to the latest version or switch to a supported browser for the best experience.", "up": "Up", diff --git a/src/components/Notifications/NotificationsList.tsx b/src/components/Notifications/NotificationsList.tsx index 0808877a444..0153017c5ed 100644 --- a/src/components/Notifications/NotificationsList.tsx +++ b/src/components/Notifications/NotificationsList.tsx @@ -222,30 +222,6 @@ export default function NotificationsList({ } }; }, [data, totalCount]); - useEffect(() => { - let intervalId: ReturnType; - if (isSubscribing) { - const checkNotificationPermission = () => { - if (Notification.permission === "denied") { - Warn({ - msg: t("notification_permission_denied"), - }); - setIsSubscribing(false); - clearInterval(intervalId); - } else if (Notification.permission === "granted") { - Success({ - msg: t("notification_permission_granted"), - }); - setIsSubscribing(false); - clearInterval(intervalId); - } - }; - - checkNotificationPermission(); - intervalId = setInterval(checkNotificationPermission, 1000); - } - return () => clearInterval(intervalId); - }, [isSubscribing]); const intialSubscriptionState = async () => { try { @@ -269,7 +245,14 @@ export default function NotificationsList({ const handleSubscribeClick = () => { const status = isSubscribed; if (status === "NotSubscribed" || status === "SubscribedOnAnotherDevice") { - subscribe(); + if (Notification.permission === "denied") { + Warn({ + msg: t("notification_permission_denied"), + }); + setIsSubscribing(false); + } else { + subscribe(); + } } else { unsubscribe(); } @@ -324,6 +307,10 @@ export default function NotificationsList({ body: data, }); + Warn({ + msg: t("unsubscribed_successfully"), + }); + setIsSubscribed("NotSubscribed"); setIsSubscribing(false); }) @@ -344,43 +331,67 @@ export default function NotificationsList({ async function subscribe() { setIsSubscribing(true); - const response = await request(routes.getPublicKey); - const public_key = response.data?.public_key; - const sw = await navigator.serviceWorker.ready; - const push = await sw.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: public_key, - }); - const p256dh = btoa( - String.fromCharCode.apply( - null, - new Uint8Array(push.getKey("p256dh") as any) as any, - ), - ); - const auth = btoa( - String.fromCharCode.apply( - null, - new Uint8Array(push.getKey("auth") as any) as any, - ), - ); + try { + const response = await request(routes.getPublicKey); + const public_key = response.data?.public_key; + const sw = await navigator.serviceWorker.ready; + const push = await sw.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: public_key, + }); + const p256dh = btoa( + String.fromCharCode.apply( + null, + new Uint8Array(push.getKey("p256dh") as any) as any, + ), + ); + const auth = btoa( + String.fromCharCode.apply( + null, + new Uint8Array(push.getKey("auth") as any) as any, + ), + ); - const data = { - pf_endpoint: push.endpoint, - pf_p256dh: p256dh, - pf_auth: auth, - }; + const data = { + pf_endpoint: push.endpoint, + pf_p256dh: p256dh, + pf_auth: auth, + }; - const { res } = await request(routes.updateUserPnconfig, { - pathParams: { username: username }, - body: data, - }); + const { res } = await request(routes.updateUserPnconfig, { + pathParams: { username: username }, + body: data, + }); - if (res?.ok) { - setIsSubscribed("SubscribedOnThisDevice"); + if (res?.ok) { + setIsSubscribed("SubscribedOnThisDevice"); + Success({ + msg: t("subscribed_successfully"), + }); + setIsSubscribing(false); + } else { + Error({ + msg: t("subscription_failed"), + }); + setIsSubscribing(false); + } + } catch (error) { + const permission = Notification.permission; + + if (permission === "denied" || permission === "default") { + Warn({ + msg: t("notification_permission_denied"), + }); + setIsSubscribing(false); + return; + } + Error({ + msg: t("subscription_failed"), + }); + } finally { + setIsSubscribing(false); } - setIsSubscribing(false); } - const handleMarkAllAsRead = async () => { setIsMarkingAllAsRead(true); await Promise.all( From fe82a57ee16602705e7d00886250e4a5d80ef4af Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Tue, 12 Nov 2024 05:47:19 +0530 Subject: [PATCH 20/99] New Cypress Test for HCX Workflow in the platform (#9007) Co-authored-by: Khavin Shankar --- .github/workflows/cypress.yaml | 2 + cypress/e2e/hcx_spec/HcxClaims.cy.ts | 97 +++++++++++++++++++ .../e2e/patient_spec/PatientHomepage.cy.ts | 1 - .../patient_spec/PatientRegistration.cy.ts | 49 ++-------- cypress/pageobject/Hcx/HcxClaims.ts | 9 ++ .../pageobject/Patient/PatientConsultation.ts | 19 ++++ cypress/pageobject/Patient/PatientCreation.ts | 4 + .../pageobject/Patient/PatientInsurance.ts | 37 +------ src/components/Facility/ConsultationCard.tsx | 2 +- src/components/HCX/PolicyEligibilityCheck.tsx | 2 + src/components/Patient/PatientHome.tsx | 1 + 11 files changed, 150 insertions(+), 73 deletions(-) create mode 100644 cypress/e2e/hcx_spec/HcxClaims.cy.ts create mode 100644 cypress/pageobject/Hcx/HcxClaims.ts diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 1104d7f480d..ecd06f922c0 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -18,6 +18,8 @@ jobs: containers: [1, 2, 3, 4] env: REACT_CARE_API_URL: http://localhost:9000 + REACT_ENABLED_APPS: "ohcnetwork/care_hcx_fe@main" + REACT_ENABLE_HCX: true steps: - name: Checkout 📥 uses: actions/checkout@v3 diff --git a/cypress/e2e/hcx_spec/HcxClaims.cy.ts b/cypress/e2e/hcx_spec/HcxClaims.cy.ts new file mode 100644 index 00000000000..2c4a6b45df9 --- /dev/null +++ b/cypress/e2e/hcx_spec/HcxClaims.cy.ts @@ -0,0 +1,97 @@ +import { HcxClaims } from "pageobject/Hcx/HcxClaims"; +import { PatientConsultationPage } from "pageobject/Patient/PatientConsultation"; +import PatientInsurance from "pageobject/Patient/PatientInsurance"; + +import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; + +describe("HCX Claims configuration and approval workflow", () => { + const loginPage = new LoginPage(); + const patientPage = new PatientPage(); + const patientConsultationPage = new PatientConsultationPage(); + const patientInsurance = new PatientInsurance(); + const hcxClaims = new HcxClaims(); + const hcxPatientName = "Dummy Patient 14"; + const firstInsuranceIdentifier = "insurance-details-0"; + const patientMemberId = "001"; + const patientPolicyId = "100"; + const patientInsurerName = "Demo Payor"; + + before(() => { + loginPage.loginAsDistrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Verify the HCX Workflow for a patient with mocked eligibility", () => { + // Modify the insurance for a facility + patientPage.visitPatient(hcxPatientName); + patientConsultationPage.clickPatientDetails(); + patientPage.clickPatientUpdateDetails(); + patientInsurance.clickAddInsruanceDetails(); + patientInsurance.typePatientInsuranceDetail( + firstInsuranceIdentifier, + "subscriber_id", + patientMemberId, + ); + patientInsurance.typePatientInsuranceDetail( + firstInsuranceIdentifier, + "policy_id", + patientPolicyId, + ); + patientInsurance.selectPatientInsurerName( + firstInsuranceIdentifier, + patientInsurerName, + ); + cy.submitButton("Save Details"); + cy.verifyNotification("Patient updated successfully"); + cy.closeNotification(); + // Navigate to Consultation View and capture dynamic consultation ID + let consultationId: string; + patientConsultationPage.clickViewConsultationButton(); + cy.url().then((url) => { + const urlRegex = + /facility\/([^/]+)\/patient\/([^/]+)\/consultation\/([^/]+)/; + const match = url.match(urlRegex); + if (match) { + consultationId = match[3]; + } + }); + // Intercept and mock the eligibility check response using captured consultationId + cy.intercept("POST", "/api/hcx/check_eligibility", (req) => { + req.reply({ + statusCode: 200, + body: { + api_call_id: "bfa228f0-cdfa-4426-bebe-26e996079dbb", + correlation_id: "86ae030c-1b33-4e52-a6f1-7a74a48111eb", + timestamp: Date.now(), + consultation: consultationId, + policy: patientPolicyId, + outcome: "Complete", + limit: 1, + }, + }); + }).as("checkEligibility"); + // Raise a HCX Pre-auth + patientConsultationPage.clickManagePatientButton(); + patientConsultationPage.clickClaimsButton(); + hcxClaims.selectEligiblePolicy(patientInsurerName); + hcxClaims.verifyPolicyEligibility(); + cy.verifyNotification("Checking Policy Eligibility"); + cy.closeNotification(); + // Confirm that the eligibility check displays as successful + cy.wait("@checkEligibility").then((interception) => { + const response = interception.response.body; + expect(response.outcome).to.equal("Complete"); + }); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts index 53fb8732712..d2dbb2a3619 100644 --- a/cypress/e2e/patient_spec/PatientHomepage.cy.ts +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -127,7 +127,6 @@ describe("Patient Homepage present functionalities", () => { patientHome.selectPatientMedicoFilter(patientMedicoStatus); patientHome.clickPatientFilterApply(); cy.get("a[data-cy='patient']").should("contain.text", "Dummy Patient"); - patientHome.verifyTotalPatientCount("1"); // Verify the presence of badges patientHome.verifyGenderBadgeContent(patientGender); patientHome.verifyCategoryBadgeContent(patientCategory); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index cb84fa06674..91810ffd273 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -7,7 +7,6 @@ import PatientTransfer from "../../pageobject/Patient/PatientTransfer"; import { generatePhoneNumber } from "../../pageobject/utils/constants"; const yearOfBirth = "2001"; -const isHCXEnabled = Cypress.env("ENABLE_HCX"); const calculateAge = () => { const currentYear = new Date().getFullYear(); @@ -58,13 +57,11 @@ describe("Patient Creation with consultation", () => { const patientOneFirstInsuranceId = "insurance-details-0"; const patientOneFirstSubscriberId = "member id 01"; const patientOneFirstPolicyId = "policy name 01"; - const patientOneFirstInsurerId = "insurer id 01"; - const patientOneFirstInsurerName = "insurer name 01"; + const patientOneFirstInsurerName = "Demo Payor"; const patientOneSecondInsuranceId = "insurance-details-1"; const patientOneSecondSubscriberId = "member id 02"; const patientOneSecondPolicyId = "policy name 02"; - const patientOneSecondInsurerId = "insurer id 02"; - const patientOneSecondInsurerName = "insurer name 02"; + const patientOneSecondInsurerName = "Care Payor"; const patientTransferPhoneNumber = "9849511866"; const patientTransferFacility = "Dummy Shifting Center"; const patientTransferName = "Dummy Patient 10"; @@ -178,21 +175,10 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneFirstPolicyId, ); - if (isHCXEnabled) { - patientInsurance.selectInsurer("test"); - } else { - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_id", - patientOneFirstInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_name", - patientOneFirstInsurerName, - ); - } - + patientInsurance.selectPatientInsurerName( + patientOneFirstInsuranceId, + patientOneFirstInsurerName, + ); patientInsurance.clickAddInsruanceDetails(); patientInsurance.typePatientInsuranceDetail( patientOneSecondInsuranceId, @@ -204,21 +190,10 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneSecondPolicyId, ); - if (isHCXEnabled) { - patientInsurance.selectInsurer("Care"); - } else { - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_id", - patientOneSecondInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_name", - patientOneSecondInsurerName, - ); - } - + patientInsurance.selectPatientInsurerName( + patientOneSecondInsuranceId, + patientOneSecondInsurerName, + ); patientPage.clickUpdatePatient(); cy.wait(3000); patientPage.verifyPatientUpdated(); @@ -247,16 +222,12 @@ describe("Patient Creation with consultation", () => { patientInsurance.verifyPatientPolicyDetails( patientOneFirstSubscriberId, patientOneFirstPolicyId, - patientOneFirstInsurerId, patientOneFirstInsurerName, - isHCXEnabled, ); patientInsurance.verifyPatientPolicyDetails( patientOneSecondSubscriberId, patientOneSecondPolicyId, - patientOneSecondInsurerId, patientOneSecondInsurerName, - isHCXEnabled, ); }); diff --git a/cypress/pageobject/Hcx/HcxClaims.ts b/cypress/pageobject/Hcx/HcxClaims.ts new file mode 100644 index 00000000000..9a915a05196 --- /dev/null +++ b/cypress/pageobject/Hcx/HcxClaims.ts @@ -0,0 +1,9 @@ +export class HcxClaims { + selectEligiblePolicy(policy: string) { + cy.clickAndSelectOption("#select-insurance-policy", policy); + } + + verifyPolicyEligibility() { + cy.verifyAndClickElement("#check-eligibility", "Check Eligibility"); + } +} diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index e0b51b9265e..6f4f994b395 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -13,6 +13,7 @@ export class PatientConsultationPage { selectSymptomsDate(date: string) { cy.clickAndTypeDate("#symptoms_onset_date", date); } + clickAddSymptom() { cy.get("#add-symptom").click(); } @@ -111,4 +112,22 @@ export class PatientConsultationPage { ); cy.wait(3000); } + + clickViewConsultationButton() { + cy.verifyAndClickElement( + "#view_consultation_updates", + "View Consultation / Consultation Updates", + ); + } + + clickManagePatientButton() { + cy.verifyAndClickElement("#show-more", "Manage Patient"); + } + + clickClaimsButton() { + cy.get("#log-update").scrollIntoView(); + cy.intercept(/\/api\/hcx\/policy\/\?.*/).as("policyStatus"); + cy.get("#consultation-buttons").contains("Claims").click(); + cy.wait("@policyStatus").its("response.statusCode").should("eq", 200); + } } diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index 41b3c02977d..8f46dec5560 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -220,6 +220,10 @@ export class PatientPage { cy.visit(patient_url + "/update"); } + clickPatientUpdateDetails() { + cy.verifyAndClickElement("#update-patient-details", "Update Details"); + } + interceptFacilities() { cy.intercept("GET", "**/facility/*/patient/**").as("getFacilities"); } diff --git a/cypress/pageobject/Patient/PatientInsurance.ts b/cypress/pageobject/Patient/PatientInsurance.ts index 1b91d27b629..79d6e3b7510 100644 --- a/cypress/pageobject/Patient/PatientInsurance.ts +++ b/cypress/pageobject/Patient/PatientInsurance.ts @@ -9,32 +9,10 @@ class PatientInsurance { }); } - selectInsurer(insurer: string) { - cy.intercept("GET", "**/api/v1/hcx/payors/**", { - statusCode: 200, - body: [ - { - name: "test payor 2", - code: "testpayor2.swasthmock@swasth-hcx-staging", - }, - { - name: "Care Payor", - code: "khavinshankar.gmail@swasth-hcx-staging", - }, - { - name: "Alliance", - code: "hcxdemo.yopmail@swasth-hcx-staging", - }, - ], - }).as("getInsurer"); - cy.get("[name='insurer']") - .last() - .click() - .type(insurer) - .then(() => { - cy.wait("@getInsurer"); - cy.get("[role='option']").contains(insurer).click(); - }); + selectPatientInsurerName(containerId: string, value: string) { + cy.get(`#${containerId}`).within(() => { + cy.typeAndSelectOption("#insurer", value); + }); } clickPatientInsuranceViewDetail() { @@ -49,18 +27,13 @@ class PatientInsurance { verifyPatientPolicyDetails( subscriberId: string, policyId: string, - insurerId: string, insurerName: string, - isHcxEnabled: string, ) { cy.get("[data-testid=patient-details]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(subscriberId); expect($dashboard).to.contain(policyId); - if (!isHcxEnabled) { - expect($dashboard).to.contain(insurerId); - expect($dashboard).to.contain(insurerName); - } + expect($dashboard).to.contain(insurerName); }); } } diff --git a/src/components/Facility/ConsultationCard.tsx b/src/components/Facility/ConsultationCard.tsx index 11fce3fbe28..213f306d730 100644 --- a/src/components/Facility/ConsultationCard.tsx +++ b/src/components/Facility/ConsultationCard.tsx @@ -169,7 +169,7 @@ export const ConsultationCard = (props: ConsultationProps) => {
navigate( diff --git a/src/components/HCX/PolicyEligibilityCheck.tsx b/src/components/HCX/PolicyEligibilityCheck.tsx index 48c63f3ca74..4515819fc26 100644 --- a/src/components/HCX/PolicyEligibilityCheck.tsx +++ b/src/components/HCX/PolicyEligibilityCheck.tsx @@ -71,6 +71,7 @@ export default function HCXPolicyEligibilityCheck({
{ )}
Date: Tue, 12 Nov 2024 06:32:35 +0530 Subject: [PATCH 21/99] Conditionaly renders pagination component if number of entries is more than results per page (#9004) --- src/components/Common/Pagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Common/Pagination.tsx b/src/components/Common/Pagination.tsx index fc6ed04447d..fb12ff66efa 100644 --- a/src/components/Common/Pagination.tsx +++ b/src/components/Common/Pagination.tsx @@ -71,7 +71,7 @@ const Pagination = ({ }; const totalCount = data.totalCount; - if (!totalCount) { + if (!totalCount || totalCount <= rowsPerPage) { return null; } const totalPage = Math.ceil(totalCount / rowsPerPage); From dc97a9cd59a7501aca2177370b45619f9e4027f6 Mon Sep 17 00:00:00 2001 From: Nithish Kumar Siliveru Date: Tue, 12 Nov 2024 09:29:50 +0530 Subject: [PATCH 22/99] Upgraded qrcode.react to latest version (#9054) --- package-lock.json | 19 ++++--------------- package.json | 5 ++--- src/components/ABDM/ABHAProfileModal.tsx | 4 ++-- src/components/Assets/AssetManage.tsx | 4 ++-- src/components/Shifting/ShiftDetails.tsx | 6 +++--- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1359528fc3..f5edb998e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "i18next-http-backend": "^2.6.2", "lodash-es": "^4.17.21", "postcss-loader": "^8.1.1", - "qrcode.react": "^3.1.0", + "qrcode.react": "^4.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", @@ -70,7 +70,6 @@ "@types/jsdom": "^21.1.7", "@types/lodash-es": "^4.17.12", "@types/node": "^22.9.0", - "@types/qrcode.react": "^1.0.5", "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-csv": "^1.1.10", @@ -5235,16 +5234,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/@types/qrcode.react": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.5.tgz", - "integrity": "sha512-BghPtnlwvrvq8QkGa1H25YnN+5OIgCKFuQruncGWLGJYOzeSKiix/4+B9BtfKF2wf5ja8yfyWYA3OXju995G8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", @@ -15130,9 +15119,9 @@ } }, "node_modules/qrcode.react": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.2.0.tgz", - "integrity": "sha512-YietHHltOHA4+l5na1srdaMx4sVSOjV9tamHs+mwiLWAMr6QVACRUw1Neax5CptFILcNoITctJY0Ipyn5enQ8g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.1.0.tgz", + "integrity": "sha512-uqXVIIVD/IPgWLYxbOczCNAQw80XCM/LulYDADF+g2xDsPj5OoRwSWtIS4jGyp295wyjKstfG1qIv/I2/rNWpQ==", "license": "ISC", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/package.json b/package.json index 6b2273e341f..60250c2209e 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "i18next-http-backend": "^2.6.2", "lodash-es": "^4.17.21", "postcss-loader": "^8.1.1", - "qrcode.react": "^3.1.0", + "qrcode.react": "^4.1.0", "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", @@ -109,7 +109,6 @@ "@types/jsdom": "^21.1.7", "@types/lodash-es": "^4.17.12", "@types/node": "^22.9.0", - "@types/qrcode.react": "^1.0.5", "@types/react": "^18.3.12", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-csv": "^1.1.10", @@ -174,4 +173,4 @@ "node": ">=22.11.0" }, "packageManager": "npm@10.9.0" -} \ No newline at end of file +} diff --git a/src/components/ABDM/ABHAProfileModal.tsx b/src/components/ABDM/ABHAProfileModal.tsx index 1e348a40778..3fc874ca86e 100644 --- a/src/components/ABDM/ABHAProfileModal.tsx +++ b/src/components/ABDM/ABHAProfileModal.tsx @@ -1,4 +1,4 @@ -import QRCode from "qrcode.react"; +import { QRCodeSVG } from "qrcode.react"; import { useRef } from "react"; import { useTranslation } from "react-i18next"; @@ -102,7 +102,7 @@ const ABHAProfileModal = ({ patientId, show, onClose, abha }: IProps) => { className="print flex flex-col gap-4 border-black sm:flex-row print:w-full print:border" >
- {

Print Preview

- +
); diff --git a/src/components/Shifting/ShiftDetails.tsx b/src/components/Shifting/ShiftDetails.tsx index 8e85fda3d19..cac6b5c41ca 100644 --- a/src/components/Shifting/ShiftDetails.tsx +++ b/src/components/Shifting/ShiftDetails.tsx @@ -1,5 +1,5 @@ import careConfig from "@careConfig"; -import QRCode from "qrcode.react"; +import { QRCodeSVG } from "qrcode.react"; import { Link, navigate } from "raviger"; import { useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; @@ -429,8 +429,8 @@ export default function ShiftDetails(props: { id: string }) {
-
From a028a6cdceb90b91682cf6b04414ca3a6b6f4d88 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:17:30 +0530 Subject: [PATCH 23/99] Refactor Cypress Auth Test to POM and Improve Test Stability (#9024) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- cypress/e2e/auth_spec/ForgotPassword.cy.ts | 17 ----- cypress/e2e/auth_spec/auth.cy.ts | 23 ------- cypress/e2e/homepage_spec/UserLogin.cy.ts | 48 ++++++++++++++ .../redirect.cy.ts | 0 cypress/pageobject/Login/LoginPage.ts | 64 +++++++++++++++++-- src/components/Auth/Login.tsx | 11 +++- 6 files changed, 117 insertions(+), 46 deletions(-) delete mode 100644 cypress/e2e/auth_spec/ForgotPassword.cy.ts delete mode 100644 cypress/e2e/auth_spec/auth.cy.ts create mode 100644 cypress/e2e/homepage_spec/UserLogin.cy.ts rename cypress/e2e/{auth_spec => homepage_spec}/redirect.cy.ts (100%) diff --git a/cypress/e2e/auth_spec/ForgotPassword.cy.ts b/cypress/e2e/auth_spec/ForgotPassword.cy.ts deleted file mode 100644 index c1b1db7ecad..00000000000 --- a/cypress/e2e/auth_spec/ForgotPassword.cy.ts +++ /dev/null @@ -1,17 +0,0 @@ -describe("Forgot Password", () => { - beforeEach(() => { - cy.awaitUrl("/", true); - cy.get("button").contains("Forgot password?").click().wait(100); - }); - - it("Send Password Reset Link", () => { - cy.get("input[id='forgot_username']").type("dummy_user_1"); - cy.contains("Send Reset Link").click().wait(1000); - cy.contains("Password Reset Email Sent").should("exist"); - }); - - it("Go to Login page", () => { - cy.get("button").contains("Back to login").click(); - cy.url().should("contain", "/"); - }); -}); diff --git a/cypress/e2e/auth_spec/auth.cy.ts b/cypress/e2e/auth_spec/auth.cy.ts deleted file mode 100644 index b2bd7b634c0..00000000000 --- a/cypress/e2e/auth_spec/auth.cy.ts +++ /dev/null @@ -1,23 +0,0 @@ -describe("Authorisation/Authentication", () => { - beforeEach(() => { - cy.awaitUrl("/", true); - }); - - it("Try login as admin with correct password", () => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); - cy.awaitUrl("/facility"); - cy.get("#user-profile-name").click(); - cy.get("#sign-out-button").contains("Sign Out").click(); - cy.url().should("include", "/"); - }); - - it("Try login as admin with incorrect password", () => { - cy.log("Logging in the user: devdistrictadmin:Coronasafe@123"); - - cy.awaitUrl("/", true); - cy.get("input[id='username']").type("devdistrictadmin"); - cy.get("input[id='password']").type("coronasafe@123"); - cy.get("button").contains("Login").click(); - cy.contains("No active account").should("exist"); - }); -}); diff --git a/cypress/e2e/homepage_spec/UserLogin.cy.ts b/cypress/e2e/homepage_spec/UserLogin.cy.ts new file mode 100644 index 00000000000..ab00bd80ff1 --- /dev/null +++ b/cypress/e2e/homepage_spec/UserLogin.cy.ts @@ -0,0 +1,48 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); +const userName = "dummy_user_1"; +const forgotPasswordHeading = "Forgot password?"; + +describe("User login workflow with correct and incorrect passwords", () => { + beforeEach(() => { + cy.awaitUrl("/", true); + }); + + it("Log in as admin with correct password", () => { + loginPage.loginManuallyAsDistrictAdmin(); + loginPage.interceptFacilityReq(); + loginPage.verifyFacilityReq(); + loginPage.ensureLoggedIn(); + loginPage.clickSignOutBtn(); + loginPage.verifyLoginPageUrl(); + }); + + it("Display an error when logging in as admin with incorrect password", () => { + loginPage.interceptLoginReq(); + loginPage.loginManuallyAsDistrictAdmin(false); + loginPage.verifyLoginReq(); + cy.verifyNotification("No active account found with the given credentials"); + cy.closeNotification(); + }); +}); + +describe("Reset user's password using email", () => { + beforeEach(() => { + cy.awaitUrl("/", true); + }); + + it("Send a password reset link and navigate back to the login page", () => { + loginPage.clickForgotPasswordButton(forgotPasswordHeading); + loginPage.verifyForgotPasswordHeading([forgotPasswordHeading]); + loginPage.fillUserNameInForgotPasswordForm(userName); + loginPage.interceptResetLinkReq(); + loginPage.clickSendResetLinkBtn(); + loginPage.verifyResetLinkReq(); + cy.verifyNotification("Password Reset Email Sent"); + cy.closeNotification(); + loginPage.clickBackButton(); + loginPage.verifyLoginPageUrl(); + loginPage.verifyLoginButtonPresence(); + }); +}); diff --git a/cypress/e2e/auth_spec/redirect.cy.ts b/cypress/e2e/homepage_spec/redirect.cy.ts similarity index 100% rename from cypress/e2e/auth_spec/redirect.cy.ts rename to cypress/e2e/homepage_spec/redirect.cy.ts diff --git a/cypress/pageobject/Login/LoginPage.ts b/cypress/pageobject/Login/LoginPage.ts index 38b8aeee2af..07eb4486d17 100644 --- a/cypress/pageobject/Login/LoginPage.ts +++ b/cypress/pageobject/Login/LoginPage.ts @@ -13,16 +13,20 @@ class LoginPage { cy.loginByApi("staffdev", "Coronasafe@123"); } - loginManuallyAsDistrictAdmin(): void { + loginManuallyAsDistrictAdmin(isCorrectCredentials: boolean = true): void { cy.get("input[id='username']").type("devdistrictadmin"); - cy.get("input[id='password']").type("Coronasafe@123"); - cy.get("button").contains("Login").click(); + if (isCorrectCredentials) { + cy.get("input[id='password']").type("Coronasafe@123"); + } else { + cy.get("input[id='password']").type("Corona"); + } + cy.submitButton("Login"); } loginManuallyAsNurse(): void { cy.get("input[id='username']").click().type("dummynurse1"); cy.get("input[id='password']").click().type("Coronasafe@123"); - cy.get("button").contains("Login").click(); + cy.submitButton("Login"); } login(username: string, password: string): void { @@ -38,6 +42,58 @@ class LoginPage { clickSignOutBtn(): void { cy.verifyAndClickElement("#sign-out-button", "Sign Out"); } + + fillUserNameInForgotPasswordForm(userName: string): void { + cy.get("#forgot_username").type(userName); + } + + clickSendResetLinkBtn(): void { + cy.verifyAndClickElement("#send-reset-link-btn", "Send Reset Link"); + } + + verifyLoginPageUrl(): void { + cy.url().should("include", "/"); + } + + clickBackButton(): void { + cy.verifyAndClickElement("#back-to-login-btn", "Back to login"); + } + + clickForgotPasswordButton(text: string): void { + cy.verifyAndClickElement("#forgot-pass-btn", text); + } + + interceptFacilityReq(): void { + cy.intercept("GET", "**/api/v1/facility/**").as("getFacilities"); + } + + verifyFacilityReq(): void { + cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); + } + + interceptLoginReq(): void { + cy.intercept("POST", "**/api/v1/auth/login").as("userLogin"); + } + + verifyLoginReq(): void { + cy.wait("@userLogin").its("response.statusCode").should("eq", 401); + } + + interceptResetLinkReq(): void { + cy.intercept("POST", "**/api/v1/password_reset").as("resetLink"); + } + + verifyResetLinkReq(): void { + cy.wait("@resetLink").its("response.statusCode").should("eq", 200); + } + + verifyLoginButtonPresence(): void { + cy.verifyContentPresence("#login-button", ["Login"]); + } + + verifyForgotPasswordHeading(text: string[]): void { + cy.verifyContentPresence("#forgot-password-heading", text); + } } export default LoginPage; diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index bcf0d4e3415..aab5f8df179 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -335,6 +335,7 @@ const Login = (props: { forgot?: boolean }) => { setForgotPassword(true); }} type="button" + id="forgot-pass-btn" className="text-sm text-primary-400 hover:text-primary-500" > {t("forget_password")} @@ -347,6 +348,7 @@ const Login = (props: { forgot?: boolean }) => {
) : ( -
+
{t("forget_password")}
-
+
{t("forget_password_instruction")} { ) : (
- } - show={show} - onClose={onClose} - className="max-w-[500px]" - fixedWidth={false} - > -
-
- -
-
- {[ - { - label: t("full_name"), - value: - abha?.name || - `${abha?.first_name} ${abha?.middle_name} ${abha?.last_name}`, - }, - { label: t("date_of_birth"), value: abha?.date_of_birth }, - { label: t("gender"), value: abha?.gender }, - { label: t("abha_number"), value: abha?.abha_number }, - { - label: t("abha_address"), - value: abha?.health_id?.split("@")[0], - }, - { label: t("email"), value: abha?.email }, - ].map((item, index) => - item.value ? ( -
-
{item.label}
-
{item.value}
-
- ) : null, - )} -
-
- -
- {abha?.created_date && ( -
- {t("created_on")}: - {formatDateTime(abha.created_date)} -
- )} - {abha?.modified_date && ( -
- {t("modified_on")}: - {formatDateTime(abha.modified_date)} -
- )} -
- - ); -}; - -export default ABHAProfileModal; diff --git a/src/components/ABDM/ConfigureHealthFacility.tsx b/src/components/ABDM/ConfigureHealthFacility.tsx deleted file mode 100644 index ba3eb325dec..00000000000 --- a/src/components/ABDM/ConfigureHealthFacility.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import { navigate } from "raviger"; -import { useReducer, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { IHealthFacility } from "@/components/ABDM/types/health-facility"; -import { Submit } from "@/components/Common/ButtonV2"; -import Loading from "@/components/Common/Loading"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useQuery from "@/Utils/request/useQuery"; -import { classNames } from "@/Utils/utils"; - -const initForm = { - health_facility: null as IHealthFacility | null, - hf_id: "", -}; - -const initialState = { - form: { ...initForm }, - errors: {} as Partial>, -}; - -const FormReducer = ( - state = initialState, - action: - | { - type: "set_form"; - form: typeof initialState.form; - } - | { - type: "set_error"; - errors: typeof initialState.errors; - }, -) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - default: - return state; - } -}; - -export interface IConfigureHealthFacilityProps { - facilityId: string; -} - -export const ConfigureHealthFacility = ( - props: IConfigureHealthFacilityProps, -) => { - const { t } = useTranslation(); - - const [state, dispatch] = useReducer(FormReducer, initialState); - const { facilityId } = props; - const [isLoading, setIsLoading] = useState(false); - - const { loading } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: facilityId }, - silent: true, - onResponse(res) { - if (res.data) { - dispatch({ - type: "set_form", - form: { - ...state.form, - health_facility: res.data, - hf_id: res.data.hf_id, - }, - }); - } - }, - }); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - - if (!state.form.hf_id) { - dispatch({ - type: "set_error", - errors: { hf_id: t("health_facility__validation__hf_id_required") }, - }); - setIsLoading(false); - return; - } - - let response = null; - let responseData = null; - if (state.form.hf_id === state.form.health_facility?.hf_id) { - const { res, data } = await request( - routes.abdm.healthFacility.registerAsService, - { - pathParams: { - facility_id: facilityId, - }, - }, - ); - response = res; - responseData = data; - } else if (state.form.health_facility) { - const { res, data } = await request( - routes.abdm.healthFacility.partialUpdate, - { - pathParams: { - facility_id: facilityId, - }, - body: { - hf_id: state.form.hf_id, - }, - }, - ); - response = res; - responseData = data; - } else { - const { res, data } = await request(routes.abdm.healthFacility.create, { - body: { - facility: facilityId, - hf_id: state.form.hf_id, - }, - silent: true, - }); - response = res; - responseData = data; - } - - if (response?.ok && responseData?.registered) { - Notification.Success({ - msg: t("health_facility__config_update_success"), - }); - navigate(`/facility/${facilityId}`); - } else { - if (responseData?.registered === false) { - Notification.Warn({ - msg: - responseData?.detail || - t("health_facility__config_registration_error"), - }); - navigate(`/facility/${facilityId}`); - } else { - Notification.Error({ - msg: - responseData?.detail || t("health_facility__config_update_error"), - }); - } - } - setIsLoading(false); - }; - - const handleChange = (e: FieldChangeEvent) => { - dispatch({ - type: "set_form", - form: { ...state.form, [e.name]: e.value }, - }); - }; - - if (loading || isLoading) { - return ; - } - - return ( -
- -
-
- - {state.form.health_facility?.registered ? ( - <> -
- - {t("health_facility__registered_1.1")}{" "} - - {t("health_facility__registered_1.2")} - - - - {t("health_facility__registered_2")} - -
- {t("health_facility__registered_3")} - - ) : ( - <> -
- - {t("health_facility__not_registered_1.1")}{" "} - - {t("health_facility__not_registered_1.2")} - - - - {t("health_facility__not_registered_2")} - - {t("health_facility__not_registered_3")} -
- - )} -

- } - required - value={state.form.hf_id} - onChange={handleChange} - error={state.errors?.hf_id} - /> -
-
-
- -
- -
- ); -}; diff --git a/src/components/ABDM/FetchRecordsModal.tsx b/src/components/ABDM/FetchRecordsModal.tsx deleted file mode 100644 index 9433374f931..00000000000 --- a/src/components/ABDM/FetchRecordsModal.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import dayjs from "dayjs"; -import { navigate } from "raviger"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import { ConsentHIType, ConsentPurpose } from "@/components/ABDM/types/consent"; -import ButtonV2 from "@/components/Common/ButtonV2"; -import DialogModal from "@/components/Common/Dialog"; -import DateFormField from "@/components/Form/FormFields/DateFormField"; -import DateRangeFormField from "@/components/Form/FormFields/DateRangeFormField"; -import { - MultiSelectFormField, - SelectFormField, -} from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; - -import { useMessageListener } from "@/hooks/useMessageListener"; - -import { ABDM_CONSENT_PURPOSE, ABDM_HI_TYPE } from "@/common/constants"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; - -const getDate = (value: string | Date) => - (value && dayjs(value).isValid() && dayjs(value).toDate()) || undefined; - -interface IProps { - abha?: AbhaNumberModel; - show: boolean; - onClose: () => void; -} - -export default function FetchRecordsModal({ abha, show, onClose }: IProps) { - const { t } = useTranslation(); - - const [idVerificationStatus, setIdVerificationStatus] = useState< - "pending" | "in-progress" | "verified" | "failed" - >("verified"); - const [purpose, setPurpose] = useState("CAREMGT"); - const [fromDate, setFromDate] = useState( - dayjs().subtract(30, "day").toDate(), - ); - const [toDate, setToDate] = useState(dayjs().toDate()); - const [isMakingConsentRequest, setIsMakingConsentRequest] = useState(false); - const [hiTypes, setHiTypes] = useState([]); - const [expiryDate, setExpiryDate] = useState( - dayjs().add(30, "day").toDate(), - ); - const [errors, setErrors] = useState>({}); - // const notificationSubscriptionState = useNotificationSubscriptionState([ - // show, - // ]); - - useMessageListener((data) => { - if (data.type === "MESSAGE" && data.from === "patients/on_find") { - if (data.message?.patient?.id === abha?.health_id) { - setIdVerificationStatus("verified"); - setErrors({ - ...errors, - health_id: "", - }); - } - } - }); - - return ( - - {/* {["unsubscribed", "subscribed_on_other_device"].includes( - notificationSubscriptionState, - ) && ( -

- {" "} - Notifications needs to be enabled on this device to verify the - patient. -

- )} */} - -
- null} - disabled - label={t("consent_request__patient_identifier")} - name="health_id" - error={errors.health_id} - className="flex-1" - /> - - {/* { - const { res } = await request(routes.abha.findPatient, { - body: { - id: abha?.health_id, - }, - reattempts: 0, - }); - - if (res?.status) { - setIdVerificationStatus("in-progress"); - } - }} - loading={idVerificationStatus === "in-progress"} - ghost={idVerificationStatus === "verified"} - disabled={ - idVerificationStatus === "verified" || - ["unsubscribed", "subscribed_on_other_device"].includes( - notificationSubscriptionState, - ) - } - className={classNames( - "mt-1.5 !py-3", - idVerificationStatus === "verified" && - "disabled:cursor-auto disabled:bg-transparent disabled:text-primary-600", - )} - > - {idVerificationStatus === "in-progress" && ( - - )} - {idVerificationStatus === "verified" && } - { - { - pending: "Verify Patient", - "in-progress": "Verifying", - verified: "Verified", - failed: "Retry", - }[idVerificationStatus] - } - */} -
- t(`consent__purpose__${o}`)} - optionValue={(o) => o} - value={purpose} - onChange={({ value }) => setPurpose(value)} - required - /> - - { - setFromDate(e.value.start!); - setToDate(e.value.end!); - }} - label={t("consent_request__date_range")} - required - /> - - { - setHiTypes(ABDM_HI_TYPE); - }} - > - {t("select_all")} - - ) - } - value={hiTypes} - optionLabel={(option) => t(`consent__hi_type__${option}`)} - optionValue={(option) => option} - onChange={(e) => setHiTypes(e.value)} - required - /> - - setExpiryDate(e.value!)} - label={t("consent_request__expiry")} - required - disablePast - /> - -
- { - if (idVerificationStatus !== "verified") { - setErrors({ - ...errors, - health_id: t("verify_patient_identifier"), - }); - - return; - } - - setIsMakingConsentRequest(true); - const { res } = await request(routes.abdm.consent.create, { - body: { - patient_abha: abha?.health_id as string, - hi_types: hiTypes, - purpose, - from_time: fromDate, - to_time: toDate, - expiry: expiryDate, - }, - }); - - if (res?.status === 201) { - Notification.Success({ - msg: t("consent_requested_successfully"), - }); - - navigate( - `/facility/${abha?.patient_object?.facility}/abdm`, - // ?? `/facility/${abha?.patient_object?.facility}/patient/${abha?.patient_object?.id}/consultation/${abha?.patient_object?.last_consultation?.id}/abdm`, - ); - } - setIsMakingConsentRequest(false); - onClose(); - }} - disabled={idVerificationStatus !== "verified"} - loading={isMakingConsentRequest} - > - {t("request_consent")} - -
-
- ); -} diff --git a/src/components/ABDM/HealthInformation.tsx b/src/components/ABDM/HealthInformation.tsx deleted file mode 100644 index e147f050131..00000000000 --- a/src/components/ABDM/HealthInformation.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { HIProfile } from "hi-profiles"; -import { useTranslation } from "react-i18next"; - -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; - -import routes from "@/Utils/request/api"; -import useQuery from "@/Utils/request/useQuery"; - -interface IProps { - artefactId: string; -} - -export default function HealthInformation({ artefactId }: IProps) { - const { t } = useTranslation(); - - const { data, loading, error } = useQuery(routes.abdm.healthInformation.get, { - pathParams: { artefactId }, - silent: true, - }); - - if (loading) { - return ; - } - - const parseData = (data: string) => { - try { - return JSON.parse(data); - } catch (e) { - return JSON.parse( - data.replace(/"/g, '\\"').replace(/'/g, '"'), // eslint-disable-line - ); - } - }; - - return ( - -
- {!!error?.is_archived && ( - <> -

- {t("hi__record_archived__title")} -

-
- {t("hi__record_archived_description")} -
-

- {t("hi__record_archived_on")}{" "} - {new Date(error?.archived_time as string).toLocaleString()} -{" "} - {error?.archived_reason as string} -

- - )} - {error && !error?.is_archived && ( - <> -

- {t("hi__record_not_fetched_title")} -

-
- {t("hi__record_not_fetched_description")} -
-

- {t("hi__waiting_for_record")} -

- - )} - {data?.data.map((item) => ( - - ))} -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx b/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx deleted file mode 100644 index d2697633bea..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx +++ /dev/null @@ -1,740 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import useMultiStepForm, { - InjectedStepProps, -} from "@/components/ABDM/LinkAbhaNumber/useMultiStepForm"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2, { ButtonWithTimer } from "@/components/Common/ButtonV2"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import OtpFormField from "@/components/Form/FormFields/OtpFormField"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { validateRule } from "@/components/Users/UserAdd"; - -import * as Notify from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; - -const MAX_OTP_RESEND_ALLOWED = 2; - -type ICreateWithAadhaarProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -type Memory = { - aadhaarNumber: string; - mobileNumber: string; - - isLoading: boolean; - validationError: string; - - transactionId: string; - abhaNumber: AbhaNumberModel | null; - - resendOtpCount: number; -}; - -export default function CreateWithAadhaar({ - onSuccess, -}: ICreateWithAadhaarProps) { - const { currentStep } = useMultiStepForm( - [ - , - , - , - , - , - , - ], - { - aadhaarNumber: "", - mobileNumber: "+91", - isLoading: false, - validationError: "", - transactionId: "", - abhaNumber: null, - resendOtpCount: 0, - }, - ); - - return
{currentStep}
; -} - -type IEnterAadhaarProps = InjectedStepProps; - -function EnterAadhaar({ memory, setMemory, next }: IEnterAadhaarProps) { - const { t } = useTranslation(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState([ - false, - false, - false, - false, - ]); - - const validateAadhaar = () => { - if ( - memory?.aadhaarNumber.length !== 12 && - memory?.aadhaarNumber.length !== 16 - ) { - setMemory((prev) => ({ - ...prev, - validationError: t("aadhaar_validation_length_error"), - })); - return false; - } - - if (memory?.aadhaarNumber.includes(" ")) { - setMemory((prev) => ({ - ...prev, - validationError: t("aadhaar_validation_space_error"), - })); - return false; - } - - return true; - }; - - const handleSubmit = async () => { - if (!validateAadhaar()) return; - - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateSendAadhaarOtp, - { - body: { - aadhaar: memory!.aadhaarNumber, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ ...prev, transactionId: data.transaction_id })); - Notify.Success({ - msg: data.detail ?? t("aadhaar_otp_send_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- - setMemory((prev) => ({ ...prev, aadhaarNumber: value })) - } - error={memory?.validationError} - /> - - {t("aadhaar_number_will_not_be_stored")} - -
- -
- {disclaimerAccepted.map((isAccepted, i) => ( - { - setDisclaimerAccepted( - disclaimerAccepted.map((v, j) => (j === i ? e.value : v)), - ); - }} - className="mr-2 rounded border-gray-700" - labelClassName="text-xs text-gray-800" - errorClassName="hidden" - /> - ))} -
- -
- !v) || - memory?.aadhaarNumber.length === 0 - } - onClick={handleSubmit} - > - {t("send_otp")} - -
-
- ); -} - -type IVerifyAadhaarProps = InjectedStepProps; - -function VerifyAadhaar({ memory, setMemory, next }: IVerifyAadhaarProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const validateMobileNumber = () => { - const phone = memory?.mobileNumber.replace("+91", "").replace(/ /g, ""); - if (phone?.length !== 10) { - setMemory((prev) => ({ - ...prev, - validationError: t("mobile_number_validation_error"), - })); - return false; - } - - return true; - }; - - const handleSubmit = async () => { - if (!validateMobileNumber()) return; - - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateVerifyAadhaarOtp, - { - body: { - otp: otp, - transaction_id: memory?.transactionId, - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - abhaNumber: data.abha_number, - resendOtpCount: 0, - })); - Notify.Success({ - msg: data.detail ?? t("otp_verification_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateSendAadhaarOtp, - { - body: { - aadhaar: memory!.aadhaarNumber, - // transaction_id: memory?.transactionId, - }, - silent: true, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: prev.resendOtpCount + 1, - })); - Notify.Success({ - msg: data.detail ?? t("aadhaar_otp_send_success"), - }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Success({ - msg: t("aadhaar_otp_send_error"), - }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- - setMemory((prev) => ({ ...prev, aadhaarNumber: value })) - } - /> - - {t("aadhaar_number_will_not_be_stored")} - -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_aadhaar_otp")} - disabled={memory?.isLoading} - /> -
- -
- } - name="mobile_number" - value={memory?.mobileNumber} - onChange={(e) => { - if (!memory?.mobileNumber.startsWith("+91")) { - setMemory((prev) => ({ - ...prev, - validationError: t("only_indian_mobile_numbers_supported"), - })); - return; - } - - setMemory((prev) => ({ ...prev, mobileNumber: e.value })); - }} - error={memory?.validationError} - errorClassName="text-xs text-red-500" - types={["mobile"]} - /> -
- -
- 6 || memory?.mobileNumber.length === 0} - onClick={handleSubmit} - > - {t("verify_otp")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} - -type IHandleExistingAbhaNumberProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function HandleExistingAbhaNumber({ - memory, - onSuccess, - next, -}: IHandleExistingAbhaNumberProps) { - const { t } = useTranslation(); - - // skip this step for new abha number - useEffect(() => { - if (memory?.abhaNumber?.new) { - next(); - } - }, [memory?.abhaNumber, memory?.mobileNumber]); // eslint-disable-line - - return ( -
-

- {t("abha_number_exists")} -

-

- {t("abha_number_exists_description")} -

-
- - {t("create_new_abha_address")} - - onSuccess(memory?.abhaNumber as AbhaNumberModel)} - > - {t("use_existing_abha_address")} - -

- {memory?.abhaNumber?.health_id} -

-
-
- ); -} - -type ILinkMobileNumberProps = InjectedStepProps; - -function LinkMobileNumber({ - memory, - goTo, - setMemory, - next, -}: ILinkMobileNumberProps) { - const { t } = useTranslation(); - - useEffect(() => { - if ( - memory?.abhaNumber?.mobile === - memory?.mobileNumber.replace("+91", "").replace(/ /g, "") - ) { - goTo(5); // skip linking mobile number - } - }, [memory?.abhaNumber, memory?.mobileNumber]); // eslint-disable-line - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateLinkMobileNumber, - { - body: { - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_send_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- } - name="mobile_number" - value={memory?.mobileNumber} - disabled={true} - onChange={() => null} - types={["mobile"]} - /> -
- -

- {t("mobile_number_different_from_aadhaar_mobile_number")} -

- -
- - {t("send_otp")} - -
-
- ); -} - -type IVerifyMobileNumberProps = InjectedStepProps; - -function VerifyMobileNumber({ - memory, - setMemory, - next, -}: IVerifyMobileNumberProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateVerifyMobileNumber, - { - body: { - transaction_id: memory?.transactionId, - otp: otp, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: 0, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_verify_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateLinkMobileNumber, - { - body: { - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: prev.resendOtpCount + 1, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_send_success"), - }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Success({ - msg: t("mobile_otp_send_error"), - }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- } - name="mobile_number" - value={memory?.mobileNumber} - disabled={true} - onChange={() => null} - types={["mobile"]} - /> -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_mobile_otp")} - disabled={memory?.isLoading} - /> -
- -
- - {t("verify_otp")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} - -type IChooseAbhaAddressProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function ChooseAbhaAddress({ - memory, - setMemory, - onSuccess, -}: IChooseAbhaAddressProps) { - const { t } = useTranslation(); - const [healthId, setHealthId] = useState(""); - const [suggestions, setSuggestions] = useState([]); - - useEffect(() => { - const fetchSuggestions = async () => { - const { res, data } = await request( - routes.abdm.healthId.abhaCreateAbhaAddressSuggestion, - { - body: { - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ ...prev, transactionId: data.transaction_id })); - setSuggestions(data.abha_addresses); - } - }; - - fetchSuggestions(); - }, [healthId, memory?.transactionId, setMemory]); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateEnrolAbhaAddress, - { - body: { - abha_address: healthId, - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - abhaNumber: data.abha_number, - })); - Notify.Success({ - msg: data.detail ?? t("abha_address_created_success"), - }); - onSuccess(data.abha_number); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
- { - setHealthId(value); - }} - /> - -
- {validateRule( - healthId.length >= 4, - t("abha_address_validation_length_error"), - false, - )} - {validateRule( - Number.isNaN(Number(healthId[0])) && healthId[0] !== ".", - t("abha_address_validation_start_error"), - false, - )} - {validateRule( - healthId[healthId.length - 1] !== ".", - t("abha_address_validation_end_error"), - false, - )} - {validateRule( - /^[0-9a-zA-Z._]+$/.test(healthId), - t("abha_address_validation_character_error"), - false, - )} -
- - {suggestions.length > 0 && ( -
-

- {t("abha_address_suggestions")} -

-
- {suggestions - .filter((suggestion) => suggestion !== healthId) - .map((suggestion) => ( -

setHealthId(suggestion)} - className="cursor-pointer rounded-md bg-primary-400 px-2.5 py-1 text-xs text-white" - > - {suggestion} -

- ))} -
-
- )} - -
- - {t("create_abha_address")} - -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx b/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx deleted file mode 100644 index 9b8150dee86..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import useMultiStepForm, { - InjectedStepProps, -} from "@/components/ABDM/LinkAbhaNumber/useMultiStepForm"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2, { ButtonWithTimer } from "@/components/Common/ButtonV2"; -import Dropdown, { DropdownItem } from "@/components/Common/Menu"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import OtpFormField from "@/components/Form/FormFields/OtpFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; - -import * as Notify from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; - -const MAX_OTP_RESEND_ALLOWED = 2; - -type ILoginWithOtpProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -type Memory = { - id: string; - - isLoading: boolean; - validationError: string; - - transactionId: string; - type: "aadhaar" | "mobile" | "abha-number" | "abha-address"; - otp_system: "abdm" | "aadhaar"; - abhaNumber: AbhaNumberModel | null; - - resendOtpCount: number; -}; - -export default function LinkWithOtp({ onSuccess }: ILoginWithOtpProps) { - const { currentStep } = useMultiStepForm( - [ - , - , - ], - { - id: "", - isLoading: false, - validationError: "", - transactionId: "", - type: "aadhaar", - otp_system: "aadhaar", - abhaNumber: null, - resendOtpCount: 0, - }, - ); - - return
{currentStep}
; -} - -type IEnterIdProps = InjectedStepProps; - -const supportedAuthMethods = ["AADHAAR_OTP", "MOBILE_OTP"]; - -function EnterId({ memory, setMemory, next }: IEnterIdProps) { - const { t } = useTranslation(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState([ - false, - false, - false, - ]); - const [authMethods, setAuthMethods] = useState([]); - - const valueType = useMemo(() => { - const id = memory?.id; - const isNumeric = !isNaN(Number(id?.trim())); - - if (isNumeric && (id?.length === 12 || id?.length === 16)) { - return "aadhaar"; - } else if (isNumeric && id?.length === 10) { - return "mobile"; - } else if (isNumeric && id?.length === 14) { - return "abha-number"; - } else { - return "abha-address"; - } - }, [memory?.id]); - - const handleGetAuthMethods = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - if (valueType === "aadhaar") { - setAuthMethods(["AADHAAR_OTP"]); - } else if (valueType === "mobile") { - setAuthMethods(["MOBILE_OTP"]); - } else { - const { res, data, error } = await request( - routes.abdm.healthId.abhaLoginCheckAuthMethods, - { - body: { - abha_address: memory?.id.replace(/-/g, "").replace(/ /g, ""), - }, - silent: true, - }, - ); - - if (res?.status === 200 && data) { - const methods = data.auth_methods.filter((method: string) => - supportedAuthMethods.find((supported) => supported === method), - ); - - if (methods.length === 0) { - Notify.Warn({ msg: t("get_auth_mode_error") }); - } - } else { - Notify.Error({ msg: error?.message ?? t("get_auth_mode_error") }); - } - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleSendOtp = async (authMethod: string) => { - if (!supportedAuthMethods.includes(authMethod)) { - Notify.Warn({ msg: t("auth_method_unsupported") }); - return; - } - - const otp_system: "aadhaar" | "abdm" = - authMethod === "AADHAAR_OTP" ? "aadhaar" : "abdm"; - - setMemory((prev) => ({ - ...prev, - isLoading: true, - type: valueType, - otp_system, - })); - - const { res, data } = await request(routes.abdm.healthId.abhaLoginSendOtp, { - body: { - value: memory?.id, - type: valueType, - otp_system, - }, - }); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - })); - Notify.Success({ msg: data.detail ?? t("send_otp_success") }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- { - setMemory((prev) => ({ ...prev, id: value })); - setAuthMethods([]); - }} - error={memory?.validationError} - /> - - {t("any_id_description")} - -
- -
- {disclaimerAccepted.map((isAccepted, i) => ( - { - setDisclaimerAccepted( - disclaimerAccepted.map((v, j) => (j === i ? e.value : v)), - ); - }} - className="mr-2 rounded border-gray-700" - labelClassName="text-xs text-gray-800" - errorClassName="hidden" - /> - ))} -
- -
- {authMethods.length === 0 ? ( - !v) || memory?.id.length === 0 - } - onClick={handleGetAuthMethods} - > - {t("get_auth_methods")} - - ) : ( - - {authMethods.map((method) => ( - handleSendOtp(method)}> - {t(`abha__auth_method__${method}`)} - - ))} - - )} -
-
- ); -} - -type IVerifyIdProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function VerifyId({ memory, setMemory, onSuccess }: IVerifyIdProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaLoginVerifyOtp, - { - body: { - type: memory?.type, - transaction_id: memory?.transactionId, - otp, - otp_system: memory?.otp_system, - }, - }, - ); - - if (res?.status === 200 && data) { - Notify.Success({ msg: t("verify_otp_success") }); - onSuccess(data.abha_number); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request(routes.abdm.healthId.abhaLoginSendOtp, { - body: { - value: memory?.id, - type: memory?.type, - otp_system: memory?.otp_system, - }, - }); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: (prev.resendOtpCount ?? 0) + 1, - })); - Notify.Success({ msg: data.detail ?? t("send_otp_success") }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Error({ msg: t("send_otp_error") }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- null} - /> - - {t("any_id_description")} - -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_otp")} - disabled={memory?.isLoading} - /> -
- -
- - {t("verify_and_link")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx b/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx deleted file mode 100644 index 712d28943b4..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { ABHAQRContent, AbhaNumberModel } from "@/components/ABDM/types/abha"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; - -type ILoginWithQrProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -export default function LinkWithQr({ onSuccess }: ILoginWithQrProps) { - const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(false); - - return ( -
- { - if (detectedCodes.length === 0) return; - - const scannedValue = detectedCodes[0].rawValue; - if (!scannedValue || isLoading) return; - - try { - const qrData = JSON.parse(scannedValue) as ABHAQRContent; - - setIsLoading(true); - const { res, data } = await request(routes.abdm.abhaNumber.create, { - body: { - abha_number: qrData.hidn, - health_id: qrData.hid || qrData.phr, - name: qrData.name, - gender: qrData.gender, - date_of_birth: qrData.dob, - address: qrData.address, - district: qrData.district_name || qrData["dist name"], - state: qrData.state_name || qrData["state name"], - mobile: qrData.mobile, - }, - }); - - if (res?.status === 201 && data) { - onSuccess(data); - } - setIsLoading(false); - } catch (e) { - Notification.Error({ - msg: t("abha__qr_scanning_error"), - }); - } - }} - onError={(e: unknown) => { - const errorMessage = e instanceof Error ? e.message : "Unknown error"; - Notification.Error({ - msg: errorMessage, - }); - }} - scanDelay={3000} - constraints={{ - facingMode: "environment", - }} - /> -
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/index.tsx b/src/components/ABDM/LinkAbhaNumber/index.tsx deleted file mode 100644 index 45cd0b34d02..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/index.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CreateWithAadhaar from "@/components/ABDM/LinkAbhaNumber/CreateWithAadhaar"; -import LinkWithOtp from "@/components/ABDM/LinkAbhaNumber/LinkWithOtp"; -import LinkWithQr from "@/components/ABDM/LinkAbhaNumber/LinkWithQr"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2 from "@/components/Common/ButtonV2"; -import DialogModal from "@/components/Common/Dialog"; - -import { classNames } from "@/Utils/utils"; - -interface ILinkAbhaNumberProps { - show: boolean; - onClose: () => void; - onSuccess: (abhaNumber: AbhaNumberModel) => void; -} - -const ABHA_LINK_OPTIONS = { - create_with_aadhaar: { - title: "abha_link_options__create_with_aadhaar__title", - description: "abha_link_options__create_with_aadhaar__description", - disabled: false, - value: "create_with_aadhaar", - create: true, - }, - link_with_otp: { - title: "abha_link_options__link_with_otp__title", - description: "abha_link_options__link_with_otp__description", - disabled: false, - value: "link_with_otp", - create: false, - }, - create_with_driving_license: { - title: "abha_link_options__create_with_driving_license__title", - description: "abha_link_options__create_with_driving_license__description", - disabled: true, - value: "create_with_driving_license", - create: true, - }, - link_with_demographics: { - title: "abha_link_options__link_with_demographics__title", - description: "abha_link_options__link_with_demographics__description", - disabled: true, - value: "link_with_demographics", - create: false, - }, - link_with_qr: { - title: "abha_link_options__link_with_qr__title", - description: "abha_link_options__link_with_qr__description", - disabled: false, - value: "link_with_qr", - create: false, - }, -}; - -export default function LinkAbhaNumber({ - show, - onClose, - onSuccess, -}: ILinkAbhaNumberProps) { - const { t } = useTranslation(); - const [currentAbhaLinkOption, setCurrentAbhaLinkOption] = useState< - keyof typeof ABHA_LINK_OPTIONS - >("create_with_aadhaar"); - - return ( - - {currentAbhaLinkOption === "create_with_aadhaar" && ( - - )} - - {currentAbhaLinkOption === "link_with_otp" && ( - - )} - - {currentAbhaLinkOption === "link_with_qr" && ( - - )} - -
-

- setCurrentAbhaLinkOption( - ABHA_LINK_OPTIONS[currentAbhaLinkOption].create - ? "link_with_otp" - : "create_with_aadhaar", - ) - } - className="cursor-pointer text-center text-sm text-blue-800" - > - {ABHA_LINK_OPTIONS[currentAbhaLinkOption].create - ? t("link_existing_abha_profile") - : t("create_new_abha_profile")} -

-
- -
-

- {t("try_different_abha_linking_option")} -

-
- {Object.values(ABHA_LINK_OPTIONS) - .filter( - (option) => - option.value !== currentAbhaLinkOption && - ABHA_LINK_OPTIONS[currentAbhaLinkOption]?.create === - option.create, - ) - .sort((a) => (a.disabled ? 1 : -1)) - .map((option) => ( - - setCurrentAbhaLinkOption( - option.value as keyof typeof ABHA_LINK_OPTIONS, - ) - } - ghost - tooltip={ - option.disabled - ? t("abha_link_options__disabled_tooltip") - : t(option.description) - } - disabled={option.disabled} - tooltipClassName="top-full mt-1" - className={classNames( - "w-full border border-gray-400 text-secondary-800", - !option.disabled && "hover:border-primary-100", - )} - > - {t(option.title)} - - ))} -
-
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts b/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts deleted file mode 100644 index 79bc9ec71c6..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - Dispatch, - ReactElement, - SetStateAction, - cloneElement, - useCallback, - useMemo, - useState, -} from "react"; - -export interface InjectedStepProps { - currentStepIndex: number; - isFirstStep: boolean; - isLastStep: boolean; - next: () => void; - prev: () => void; - goTo: (step: number) => void; - memory: T | null; - setMemory: Dispatch>; -} - -export default function useMultiStepForm( - steps: ReactElement[], - initialValues?: T, -) { - const [currentStepIndex, setCurrentStepIndex] = useState(0); - const [memory, setMemory] = useState(initialValues as T); - - const next = useCallback( - () => - setCurrentStepIndex((prev) => - steps.length - 1 > prev ? prev + 1 : prev, - ), - [steps.length], - ); - - const prev = useCallback( - () => setCurrentStepIndex((prev) => (prev > 0 ? prev - 1 : prev)), - [], - ); - - const goTo = useCallback( - (step: number) => - setCurrentStepIndex((prev) => - step >= 0 && step <= steps.length - 1 ? step : prev, - ), - [steps.length], - ); - - const options = useMemo( - () => ({ - currentStepIndex, - isFirstStep: currentStepIndex === 0, - isLastStep: currentStepIndex === steps.length - 1, - next, - prev, - goTo, - memory, - setMemory, - }), - [currentStepIndex, memory, next, prev, goTo, steps.length], - ); - - const currentStep = cloneElement(steps[currentStepIndex], { - ...options, - ...steps[currentStepIndex].props, - }); - - return { currentStep, ...options }; -} diff --git a/src/components/ABDM/types/abha.ts b/src/components/ABDM/types/abha.ts deleted file mode 100644 index f189bd111cb..00000000000 --- a/src/components/ABDM/types/abha.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PatientModel } from "@/components/Patient/models"; - -export type AbhaNumberModel = { - id: number; - external_id: string; - created_date: string; - modified_date: string; - abha_number: string; - health_id: string; - name: string; - first_name: string | null; - middle_name: string | null; - last_name: string | null; - gender: "F" | "M" | "O"; - date_of_birth: string | null; - address: string | null; - district: string | null; - state: string | null; - pincode: string | null; - mobile: string | null; - email: string | null; - profile_photo: string | null; - new: boolean; - patient: string | null; - patient_object: PatientModel | null; -}; - -export type ABHAQRContent = { - hidn: string; - name: string; - gender: "M" | "F" | "O"; - dob: string; - mobile: string; - address: string; - distlgd: string; - statelgd: string; -} & ({ hid: string; phr?: never } | { phr: string; hid?: never }) & - ( - | { district_name: string; "dist name"?: never } - | { "dist name": string; district_name?: never } - ) & - ( - | { state_name: string; "state name"?: never } - | { "state name": string; state_name?: never } - ); diff --git a/src/components/ABDM/types/consent.ts b/src/components/ABDM/types/consent.ts deleted file mode 100644 index a5041f5767d..00000000000 --- a/src/components/ABDM/types/consent.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import { UserBaseModel } from "@/components/Users/models"; - -export type ConsentPurpose = - | "CAREMGT" - | "BTG" - | "PUBHLTH" - | "HPAYMT" - | "DSRCH" - | "PATRQT"; - -export type ConsentStatus = - | "REQUESTED" - | "GRANTED" - | "DENIED" - | "EXPIRED" - | "REVOKED"; - -export type ConsentHIType = - | "Prescription" - | "DiagnosticReport" - | "OPConsultation" - | "DischargeSummary" - | "ImmunizationRecord" - | "HealthDocumentRecord" - | "WellnessRecord"; - -export type ConsentAccessMode = "VIEW" | "STORE" | "QUERY" | "STREAM"; - -export type ConsentFrequencyUnit = "HOUR" | "DAY" | "WEEK" | "MONTH" | "YEAR"; - -export type ConsentCareContext = { - patientReference: string; - careContextReference: string; -}; - -export type ConsentModel = { - id: string; - consent_id: null | string; - - patient_abha: string; - care_contexts: ConsentCareContext[]; - - status: ConsentStatus; - purpose: ConsentPurpose; - hi_types: ConsentHIType[]; - - access_mode: ConsentAccessMode; - from_time: string; - to_time: string; - expiry: string; - - frequency_unit: ConsentFrequencyUnit; - frequency_value: number; - frequency_repeats: number; - - hip: null | string; - hiu: null | string; - - created_date: string; - modified_date: string; -}; - -export type CreateConsentTBody = { - patient_abha: string; - hi_types: ConsentHIType[]; - purpose: ConsentPurpose; - from_time: Date | string; - to_time: Date | string; - expiry: Date | string; - - access_mode?: ConsentAccessMode; - frequency_unit?: ConsentFrequencyUnit; - frequency_value?: number; - frequency_repeats?: number; - hip?: null | string; -}; - -export type ConsentArtefactModel = { - consent_request: string; - - cm: null | string; -} & ConsentModel; - -export type ConsentRequestModel = { - requester: UserBaseModel; - patient_abha_object: AbhaNumberModel; - consent_artefacts: ConsentArtefactModel[]; -} & ConsentModel; diff --git a/src/components/ABDM/types/health-facility.ts b/src/components/ABDM/types/health-facility.ts deleted file mode 100644 index 419003dbf8e..00000000000 --- a/src/components/ABDM/types/health-facility.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface IHealthFacility { - id: string; - registered: boolean; - external_id: string; - created_date: string; - modified_date: string; - hf_id: string; - facility: string; - detail?: string; -} - -export interface IcreateHealthFacilityTBody { - facility: string; - hf_id: string; -} - -export interface IpartialUpdateHealthFacilityTBody { - hf_id: string; -} diff --git a/src/components/ABDM/types/health-information.ts b/src/components/ABDM/types/health-information.ts deleted file mode 100644 index eeb1c53a4fb..00000000000 --- a/src/components/ABDM/types/health-information.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type HealthInformationModel = { - data: { - content: string; - care_context_reference: string; - }[]; -}; diff --git a/src/components/Facility/ConsultationDetails/ConsultationContext.tsx b/src/components/Facility/ConsultationDetails/ConsultationContext.tsx new file mode 100644 index 00000000000..174b20536a5 --- /dev/null +++ b/src/components/Facility/ConsultationDetails/ConsultationContext.tsx @@ -0,0 +1,73 @@ +import { ReactNode, createContext, useContext, useState } from "react"; + +import { ConsultationModel } from "@/components/Facility/models"; +import { PatientModel } from "@/components/Patient/models"; + +import { PLUGIN_Component } from "@/PluginEngine"; + +interface ConsultationContextBase { + consultation?: ConsultationModel; + patient?: PatientModel; +} + +type ConsultationContextType = ConsultationContextBase & + T & { + setValue: ( + key: K, + value: (ConsultationContextBase & T)[K], + ) => void; + }; + +const ConsultationContext = createContext< + ConsultationContextType | undefined +>(undefined); + +export const useConsultation = () => { + const context = useContext(ConsultationContext); + + if (!context) { + throw new Error( + "'useConsultation' must be used within 'ConsultationProvider' only", + ); + } + + return context as ConsultationContextType; +}; + +interface ConsultationProviderProps { + children: ReactNode; + initialContext?: Partial; +} + +export const ConsultationProvider = ({ + children, + initialContext = {}, +}: ConsultationProviderProps) => { + const [state, setState] = useState( + initialContext as ConsultationContextBase & T, + ); + + const setValue = ( + key: K, + value: (ConsultationContextBase & T)[K], + ) => { + setState((prevState) => ({ + ...prevState, + [key]: value, + })); + }; + + return ( + + } + > + + {children} + + ); +}; diff --git a/src/components/Facility/ConsultationDetails/index.tsx b/src/components/Facility/ConsultationDetails/index.tsx index 496fd8f5d02..ec26dfe1a1d 100644 --- a/src/components/Facility/ConsultationDetails/index.tsx +++ b/src/components/Facility/ConsultationDetails/index.tsx @@ -1,9 +1,7 @@ -import { Link, navigate, useQueryParams } from "raviger"; +import { Link, navigate } from "raviger"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import ABDMRecordsTab from "@/components/ABDM/ABDMRecordsTab"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; import RelativeDateUserMention from "@/components/Common/RelativeDateUserMention"; @@ -29,6 +27,7 @@ import PatientInfoCard from "@/components/Patient/PatientInfoCard"; import { PatientModel } from "@/components/Patient/models"; import useAuthUser from "@/hooks/useAuthUser"; +import { useCareAppConsultationTabs } from "@/hooks/useCareApps"; import { GENDER_TYPES } from "@/common/constants"; @@ -44,6 +43,8 @@ import { relativeTime, } from "@/Utils/utils"; +import { ConsultationProvider } from "./ConsultationContext"; + export interface ConsultationTabProps { consultationId: string; facilityId: string; @@ -52,7 +53,7 @@ export interface ConsultationTabProps { patientData: PatientModel; } -const TABS = { +const defaultTabs = { UPDATES: ConsultationUpdatesTab, FEED: ConsultationFeedTab, SUMMARY: ConsultationSummaryTab, @@ -66,20 +67,27 @@ const TABS = { NUTRITION: ConsultationNutritionTab, PRESSURE_SORE: ConsultationPressureSoreTab, DIALYSIS: ConsultationDialysisTab, - ABDM: ABDMRecordsTab, -}; +} as Record>; export const ConsultationDetails = (props: any) => { const { facilityId, patientId, consultationId } = props; const { t } = useTranslation(); + const [tabs, setTabs] = + useState>>(defaultTabs); + const pluginTabs = useCareAppConsultationTabs(); + + useEffect(() => { + if (pluginTabs) { + setTabs((prev) => ({ ...prev, ...pluginTabs })); + } + }, [pluginTabs]); + let tab = undefined; - if (Object.keys(TABS).includes(props.tab.toUpperCase())) { - tab = props.tab.toUpperCase() as keyof typeof TABS; + if (Object.keys(tabs).includes(props.tab.toUpperCase())) { + tab = props.tab.toUpperCase(); } const [showDoctors, setShowDoctors] = useState(false); - const [qParams, _] = useQueryParams(); const [patientData, setPatientData] = useState(); - const [abhaNumberData, setAbhaNumberData] = useState(); const [activeShiftingData, setActiveShiftingData] = useState>([]); const getPatientGender = (patientData: any) => @@ -144,16 +152,6 @@ export const ConsultationDetails = (props: any) => { const fetchData = useCallback( async (id: string) => { - // Get abha number data - const { data: abhaNumberData } = await request( - routes.abdm.abhaNumber.get, - { - pathParams: { abhaNumberId: id ?? "" }, - silent: true, - }, - ); - setAbhaNumberData(abhaNumberData); - // Get shifting data const shiftRequestsQuery = await request(routes.listShiftRequests, { query: { patient: id }, @@ -194,7 +192,7 @@ export const ConsultationDetails = (props: any) => { return ; } - const SelectedTab = TABS[tab]; + const SelectedTab = tabs[tab]; const tabButtonClasses = (selected: boolean) => `capitalize min-w-max-content cursor-pointer font-bold whitespace-nowrap ${ @@ -204,217 +202,222 @@ export const ConsultationDetails = (props: any) => { }`; return ( -
+
- -
-
- { - consultationQuery.refetch(); - patientDataQuery.refetch(); +
+ +
+
+ { + consultationQuery.refetch(); + patientDataQuery.refetch(); + }} + consultationId={consultationId} + activeShiftingData={activeShiftingData} + /> -
- {consultationData.admitted_to && ( -
-
- Patient - {consultationData.discharge_date - ? " Discharged from" - : " Admitted to"} - - {consultationData.admitted_to} - -
- {(consultationData.discharge_date ?? - consultationData.encounter_date) && ( -
- {relativeTime( - consultationData.discharge_date - ? consultationData.discharge_date - : consultationData.encounter_date, - )} +
+ {consultationData.admitted_to && ( +
+
+ Patient + {consultationData.discharge_date + ? " Discharged from" + : " Admitted to"} + + {consultationData.admitted_to} + +
+ {(consultationData.discharge_date ?? + consultationData.encounter_date) && ( +
+ {relativeTime( + consultationData.discharge_date + ? consultationData.discharge_date + : consultationData.encounter_date, + )} +
+ )} +
+ {consultationData.encounter_date && + formatDateTime(consultationData.encounter_date)} + {consultationData.discharge_date && + ` - ${formatDateTime(consultationData.discharge_date)}`}
- )} -
- {consultationData.encounter_date && - formatDateTime(consultationData.encounter_date)} - {consultationData.discharge_date && - ` - ${formatDateTime(consultationData.discharge_date)}`}
-
- )} -
-
-
-
- Created:   - -
+ )}
-
-
- Last Modified: -   - +
+
+
+ Created:   + +
+
+
+
+ Last Modified: +   + +
-
- {!!consultationData.diagnoses?.length && ( -
-
- + {!!consultationData.diagnoses?.length && ( +
+
+ +
-
- )} -
-
-
- +
+
- -
- - - {showPatientNotesPopup && ( - - )} -
+ + {showPatientNotesPopup && ( + + )} +
+ ); }; diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 78d2edd3058..1cb7c7d69cf 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -2,13 +2,13 @@ import { t } from "i18next"; import { navigate } from "raviger"; import { useReducer, useState } from "react"; -import { ConfigureHealthFacility } from "@/components/ABDM/ConfigureHealthFacility"; import { Submit } from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; +import { PLUGIN_Component } from "@/PluginEngine"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; @@ -163,7 +163,10 @@ export const FacilityConfigure = (props: IProps) => {
- +
); diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 843b958c67b..02c3f5386cb 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -38,6 +38,7 @@ import { USER_TYPES, } from "@/common/constants"; +import { PLUGIN_Component } from "@/PluginEngine"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import { CameraFeedPermittedUserTypes } from "@/Utils/permissions"; @@ -423,13 +424,10 @@ export const FacilityHome = ({ facilityId }: Props) => { > {t("view_users")} - navigate(`/facility/${facilityId}/abdm`)} - icon={} - > - {t("view_abdm_records")} - + {hasPermissionToDeleteFacility ? ( { return `${skills[0]}, ${skills[1]} and ${skills.length - 2} other skills...`; }; -export default function PatientInfoCard(props: { +export interface PatientInfoCardProps { patient: PatientModel; consultation?: ConsultationModel; - abhaNumber?: AbhaNumberModel; fetchPatientData?: (state: { aborted: boolean }) => void; activeShiftingData: any; consultationId: string; - showAbhaProfile?: boolean; -}) { +} + +export default function PatientInfoCard(props: PatientInfoCardProps) { const authUser = useAuthUser(); const { t } = useTranslation(); const [open, setOpen] = useState(false); - const [showLinkABHANumber, setShowLinkABHANumber] = useState(false); - const [showABHAProfile, setShowABHAProfile] = useState( - !!props.showAbhaProfile, - ); - const [showFetchABDMRecords, setShowFetchABDMRecords] = useState(false); const [openDischargeSummaryDialog, setOpenDischargeSummaryDialog] = useState(false); const [openDischargeDialog, setOpenDischargeDialog] = useState(false); @@ -149,11 +130,6 @@ export default function PatientInfoCard(props: { prefetch: !!consultation?.treating_physician_object?.username, }); - const { data: healthFacility } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: patient.facility ?? "" }, - silent: true, - }); - return ( <> -
- {careConfig.abdm.enabled && - (props.abhaNumber ? ( - <> - - {({ close }) => ( - <> -
{ - close(); - setShowABHAProfile(true); - triggerGoal("Patient Card Button Clicked", { - buttonName: t("show_abha_profile"), - consultationId: consultation?.id, - userId: authUser?.id, - }); - }} - > - - {t("show_abha_profile")} -
-
{ - close(); - setShowFetchABDMRecords(true); - triggerGoal("Patient Card Button Clicked", { - buttonName: t("hi__fetch_records"), - consultationId: consultation?.id, - userId: authUser?.id, - }); - }} - > - - {t("hi__fetch_records")} -
- - )} -
- - ) : ( - - - - - {({ close, disabled }) => ( -
{ - close(); - setShowLinkABHANumber(true); - }} - > - - -

{t("generate_link_abha")}

-
-
- )} -
-
- - {!healthFacility && ( - - {t("abha_disabled_due_to_no_health_facility")} - - )} -
-
- ))} -
{!consultation?.discharge_date && ( @@ -966,46 +858,8 @@ export default function PatientInfoCard(props: {
- setShowLinkABHANumber(false)} - onSuccess={async (abhaProfile) => { - const { res, data } = await request( - routes.abdm.healthId.linkAbhaNumberAndPatient, - { - body: { - patient: patient.id, - abha_number: abhaProfile.external_id, - }, - }, - ); - - if (res?.status === 200 && data) { - Notification.Success({ - msg: t("abha_number_linked_successfully"), - }); - props.fetchPatientData?.({ aborted: false }); - setShowLinkABHANumber(false); - setShowABHAProfile(true); - } else { - Notification.Error({ - msg: t("failed_to_link_abha_number"), - }); - } - }} - /> - setShowABHAProfile(false)} - /> - setShowFetchABDMRecords(false)} - /> + ); } diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 4dc8b4965bc..5cc958d44de 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -7,16 +7,6 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Button } from "@/components/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -import LinkAbhaNumber from "@/components/ABDM/LinkAbhaNumber/index"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; import AccordionV2 from "@/components/Common/AccordionV2"; import ButtonV2 from "@/components/Common/ButtonV2"; import CollapseV2 from "@/components/Common/CollapseV2"; @@ -40,7 +30,6 @@ import { RequiredFieldValidator, } from "@/components/Form/FieldValidators"; import Form from "@/components/Form/Form"; -import { FormContextValue } from "@/components/Form/FormContext"; import AutocompleteFormField from "@/components/Form/FormFields/Autocomplete"; import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; import DateFormField from "@/components/Form/FormFields/DateFormField"; @@ -80,8 +69,10 @@ import countryList from "@/common/static/countries.json"; import { statusType, useAbortableEffect } from "@/common/utils"; import { validatePincode } from "@/common/validation"; +import { PLUGIN_Component } from "@/PluginEngine"; import { RestoreDraftButton } from "@/Utils/AutoSave"; import * as Notification from "@/Utils/Notifications"; +import { usePubSub } from "@/Utils/pubsubContext"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; @@ -94,7 +85,7 @@ import { scrollTo, } from "@/Utils/utils"; -type PatientForm = PatientModel & +export type PatientForm = PatientModel & PatientMeta & { age?: number; is_postpartum?: boolean }; interface PatientRegisterProps extends PatientModel { @@ -156,7 +147,6 @@ const initForm: any = { number_of_doses: "0", vaccine_name: null, last_vaccinated_date: null, - abha_number: null, ...medicalHistoryChoices, ration_card_category: null, }; @@ -226,7 +216,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { patientList: Array; }>({ patientList: [] }); const [patientName, setPatientName] = useState(""); - const [showLinkAbhaNumberModal, setShowLinkAbhaNumberModal] = useState(false); const [showAutoFilledPincode, setShowAutoFilledPincode] = useState(false); const [insuranceDetails, setInsuranceDetails] = useState( [], @@ -236,6 +225,8 @@ export const PatientRegister = (props: PatientRegisterProps) => { const [insuranceDetailsError, setInsuranceDetailsError] = useState(); + const { publish } = usePubSub(); + const headerText = !id ? "Add Details of Patient" : "Update Patient Details"; const buttonText = !id ? "Add Patient" : "Save Details"; @@ -287,13 +278,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { const { res, data } = await request(routes.getPatient, { pathParams: { id: id ? id : 0 }, }); - const { data: abhaNumberData } = await request( - routes.abdm.abhaNumber.get, - { - pathParams: { abhaNumberId: id ?? "" }, - silent: true, - }, - ); if (!status.aborted) { if (res?.ok && data) { @@ -306,8 +290,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { age: data.year_of_birth ? new Date().getFullYear() - data.year_of_birth : "", - health_id_number: abhaNumberData?.abha_number || "", - health_id: abhaNumberData?.health_id || "", nationality: data.nationality ? data.nationality : "India", gender: data.gender ? data.gender : undefined, state: data.state ? data.state : "", @@ -392,11 +374,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { [id], ); - const { data: healthFacility } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: facilityId }, - silent: true, - }); - useQuery(routes.hcx.policies.list, { query: { patient: id, @@ -708,27 +685,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { controllerRef: submitController, }); if (res?.ok && requestData) { - if (state.form.abha_number) { - const { res, data } = await request( - routes.abdm.healthId.linkAbhaNumberAndPatient, - { - body: { - patient: requestData.id, - abha_number: state.form.abha_number, - }, - }, - ); - - if (res?.status === 200 && data) { - Notification.Success({ - msg: t("abha_number_linked_successfully"), - }); - } else { - Notification.Error({ - msg: t("failed_to_link_abha_number"), - }); - } - } + publish("patient:upsert", requestData); await Promise.all( insuranceDetails.map(async (obj) => { @@ -769,68 +726,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { setIsLoading(false); }; - const populateAbhaValues = ( - abhaProfile: AbhaNumberModel, - field: FormContextValue, - ) => { - const values = { - abha_number: abhaProfile.external_id, - health_id_number: abhaProfile.abha_number, - health_id: abhaProfile.health_id, - }; - - if (abhaProfile.name) - field("name").onChange({ - name: "name", - value: abhaProfile.name, - }); - - if (abhaProfile.mobile) { - field("phone_number").onChange({ - name: "phone_number", - value: parsePhoneNumber(abhaProfile.mobile, "IN"), - }); - - field("emergency_phone_number").onChange({ - name: "emergency_phone_number", - value: parsePhoneNumber(abhaProfile.mobile, "IN"), - }); - } - - if (abhaProfile.gender) - field("gender").onChange({ - name: "gender", - value: { M: "1", F: "2", O: "3" }[abhaProfile.gender], - }); - - if (abhaProfile.date_of_birth) - field("date_of_birth").onChange({ - name: "date_of_birth", - value: new Date(abhaProfile.date_of_birth), - }); - - if (abhaProfile.pincode) - field("pincode").onChange({ - name: "pincode", - value: abhaProfile.pincode, - }); - - if (abhaProfile.address) { - field("address").onChange({ - name: "address", - value: abhaProfile.address, - }); - - field("permanent_address").onChange({ - name: "permanent_address", - value: abhaProfile.address, - }); - } - - dispatch({ type: "set_form", form: { ...state.form, ...values } }); - setShowLinkAbhaNumberModal(false); - }; - const handleMedicalCheckboxChange = (e: any, id: number, field: any) => { const values = field("medical_history").value ?? []; if (e.value) { @@ -1038,32 +933,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { result in duplication of patient records.

- {!state.form.abha_number && ( -
- - - - - - {!healthFacility && ( - - {t("abha_disabled_due_to_no_health_facility")} - - )} - - -
- )} {showAlertMessage.show && ( { show /> )} - {careConfig.abdm.enabled && ( -
- {showLinkAbhaNumberModal && ( - setShowLinkAbhaNumberModal(false)} - onSuccess={(data) => { - if (id) { - Notification.Warn({ - msg: "To link Abha Number, please save the patient details", - }); - } - - populateAbhaValues(data, field); - }} - /> - )} - {state.form.abha_number && ( -
-
- null} - disabled={true} - error="" - /> -
-
- {state.form.health_id ? ( - null} - disabled={true} - error="" - /> - ) : ( -
- No Abha Address Associated with this ABHA Number -
- )} -
-
- )} -
- )} +

Personal Details diff --git a/src/components/Users/UserAdd.tsx b/src/components/Users/UserAdd.tsx index b9f5d5fda7c..4c6c5edde2a 100644 --- a/src/components/Users/UserAdd.tsx +++ b/src/components/Users/UserAdd.tsx @@ -150,7 +150,7 @@ const getDate = (value: any) => export const validateRule = ( condition: boolean, content: JSX.Element | string, - isInitialState: boolean, + isInitialState: boolean = false, ) => { return (
diff --git a/src/hooks/useCareApps.ts b/src/hooks/useCareApps.ts index 5d0de6f3b9f..6870736861a 100644 --- a/src/hooks/useCareApps.ts +++ b/src/hooks/useCareApps.ts @@ -24,6 +24,14 @@ export const useCareAppNavItems = () => { return navItems; }; +export const useCareAppConsultationTabs = () => { + const careApps = useCareApps(); + + return careApps.reduce((acc, app) => { + return { ...acc, ...(app.consultationTabs ?? {}) }; + }, {}); +}; + // If required; Reduce plugin.routes to a single pluginRoutes object of type Record JSX.Element> export function usePluginRoutes() { const careApps = useCareApps(); diff --git a/src/pluginTypes.ts b/src/pluginTypes.ts index 13886b5b7d5..6a10784926e 100644 --- a/src/pluginTypes.ts +++ b/src/pluginTypes.ts @@ -1,12 +1,16 @@ import { LazyExoticComponent } from "react"; import { INavItem } from "@/components/Common/Sidebar/Sidebar"; -import { ConsultationModel } from "@/components/Facility/models"; -import { PatientModel } from "@/components/Patient/models"; +import { ConsultationModel, FacilityModel } from "@/components/Facility/models"; import { UserAssignedModel } from "@/components/Users/models"; -import { AppRoutes } from "@/Routers/AppRouter"; -import { pluginMap } from "@/pluginMap"; +import { AppRoutes } from "./Routers/AppRouter"; +import { ConsultationTabProps } from "./components/Facility/ConsultationDetails"; +import { FormContextValue } from "./components/Form/FormContext"; +import { PatientInfoCardProps } from "./components/Patient/PatientInfoCard"; +import { PatientForm } from "./components/Patient/PatientRegister"; +import { PatientModel } from "./components/Patient/models"; +import { pluginMap } from "./pluginMap"; // Define the available plugins export type AvailablePlugin = "@apps/care_livekit_fe" | "@apps/care_hcx_fe"; @@ -19,6 +23,8 @@ export type DoctorConnectButtonComponentType = React.FC<{ user: UserAssignedModel; }>; +export type ExtendPatientInfoCardComponentType = React.FC; + export type ManagePatientOptionsComponentType = React.FC<{ consultation: ConsultationModel | undefined; patient: PatientModel; @@ -28,11 +34,39 @@ export type AdditionalDischargeProceduresComponentType = React.FC<{ consultation: ConsultationModel; }>; +export type ManageFacilityOptionsComponentType = React.FC<{ + facility?: FacilityModel; +}>; + +export type ExtendFacilityConfigureComponentType = React.FC<{ + facilityId: string; +}>; + +export type ExtendPatientRegisterFormComponentType = React.FC<{ + facilityId: string; + patientId?: string; + state: { + form: { + [key: string]: any; + }; + errors: { + [key: string]: string; + }; + }; + dispatch: React.Dispatch; + field: FormContextValue; +}>; + // Define supported plugin components export type SupportedPluginComponents = { DoctorConnectButtons: DoctorConnectButtonComponentType; + ExtendPatientInfoCard: ExtendPatientInfoCardComponentType; ManagePatientOptions: ManagePatientOptionsComponentType; AdditionalDischargeProcedures: AdditionalDischargeProceduresComponentType; + ManageFacilityOptions: ManageFacilityOptionsComponentType; + ConsultationContextEnabler: React.FC; + ExtendFacilityConfigure: ExtendFacilityConfigureComponentType; + ExtendPatientRegisterForm: ExtendPatientRegisterFormComponentType; }; // Create a type for lazy-loaded components @@ -56,6 +90,10 @@ export type PluginManifest = { extends: SupportedPluginExtensions[]; components: PluginComponentMap; navItems: INavItem[]; + consultationTabs?: Record< + string, + LazyComponent> + >; }; // Create a type that ensures only available plugins can be used From 4745e6d0b22b5aa0f92b6c461654aef16a189025 Mon Sep 17 00:00:00 2001 From: Suhail Ahmed <92672136+Suhail2701@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:15:11 +0530 Subject: [PATCH 27/99] Center align `Loading` component based on remaining screen height (#8976) Co-authored-by: rithviknishad --- src/components/Common/Loading.tsx | 43 ++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/components/Common/Loading.tsx b/src/components/Common/Loading.tsx index 212a31e17e2..dd8013546d4 100644 --- a/src/components/Common/Loading.tsx +++ b/src/components/Common/Loading.tsx @@ -1,16 +1,41 @@ -const img = "/images/care_logo_gray.svg"; +import { useEffect, useRef, useState } from "react"; const Loading = () => { + const containerRef = useRef(null); + const [offsetTop, setOffsetTop] = useState(0); + + useEffect(() => { + const calculateOffset = () => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + setOffsetTop(rect.top); + } + }; + + calculateOffset(); + + window.addEventListener("resize", calculateOffset); + window.addEventListener("scroll", calculateOffset, true); + + return () => { + window.removeEventListener("resize", calculateOffset); + window.removeEventListener("scroll", calculateOffset, true); + }; + }, []); + return ( -
-
-
-
- logo -
-
-
+
+ loading
); }; + export default Loading; From 7cb54b39ced398d814de5e4e3f0d62914cbe74ec Mon Sep 17 00:00:00 2001 From: Vysakh Premkumar <84713473+tellmeY18@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:57:50 +0530 Subject: [PATCH 28/99] added non-core-qn workflow (#8889) * added non-core-qn workflow * replaced bash with js * made it to single step * updated to use env * setup variables as secrets * applied coderabbitai suggested changes * Improved Slack message. * updated the script to use vars instead of secrets uu * use slack workflow instead of slCK APPS slack --- .github/workflows/notify-non-core-qn.yml | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/workflows/notify-non-core-qn.yml diff --git a/.github/workflows/notify-non-core-qn.yml b/.github/workflows/notify-non-core-qn.yml new file mode 100644 index 00000000000..7aa59e6e868 --- /dev/null +++ b/.github/workflows/notify-non-core-qn.yml @@ -0,0 +1,87 @@ +name: Notify Core Team on Non-Core Questions +on: + issue_comment: + types: [created] +permissions: + issues: write + pull-requests: write +jobs: + notify_core_team: + runs-on: ubuntu-latest + env: + ALLOWED_USERNAMES: ${{ vars.ALLOWED_USERNAMES }} + QUESTION_KEYWORDS: ${{ vars.QUESTION_KEYWORDS }} + QUESTION_LABELS: ${{ vars.QUESTION_LABELS }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + steps: + - name: Check and Notify + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + console.log('Script started'); + const isOrgMember = (commenter, allowedUsers) => { + return allowedUsers.split(',').map(u => u.trim()).includes(commenter); + }; + const containsQuestionKeywords = (text, keywords) => { + return keywords.split(',').map(k => k.trim()).some(keyword => + text.toLowerCase().includes(keyword.toLowerCase()) + ); + }; + const addLabelsToIssue = async (github, context, labelsString) => { + const labels = labelsString.split(',').map(label => label.trim()).filter(label => label); + if (labels.length > 0) { + console.log('Adding labels:', labels); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: labels + }); + } + }; + const sendSlackNotification = async (webhook, payload) => { + const response = await fetch(webhook, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + }; + const commenter = context.payload.comment.user.login; + console.log('Commenter:', commenter); + if (!isOrgMember(commenter, process.env.ALLOWED_USERNAMES)) { + const commentBody = context.payload.comment.body; + const sanitizedComment = commentBody + ?.replace(/[^\w\s?]/gi, '') + .toLowerCase(); + console.log('Comment body:', sanitizedComment); + if (containsQuestionKeywords(sanitizedComment, process.env.QUESTION_KEYWORDS)) { + try { + console.log('Adding labels to the issue'); + await addLabelsToIssue(github, context, process.env.QUESTION_LABELS); + console.log('Labels added successfully'); + const issueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${context.payload.issue.number}`; + const issueTitle = context.payload.issue.title; + const issueNumber = context.payload.issue.number; + console.log('Issue URL:', issueUrl); + console.log('Issue Title:', issueTitle); + console.log('Issue Number:', issueNumber); + const payload = { + link: issueUrl, + Question: commentBody, + "issue-number": issueNumber, + title: issueTitle, + user: commenter + }; + await sendSlackNotification(process.env.SLACK_WEBHOOK, payload); + console.log('Slack notification sent successfully'); + } catch (error) { + console.error('Workflow failed:', error.message); + core.setFailed(`Workflow failed: ${error.message}`); + } + } + } + console.log('Script ended'); From 5bb46fa123c6362f9ef545f8682786c555394d04 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 13 Nov 2024 16:00:16 +0530 Subject: [PATCH 29/99] Migrated Scribe to a CARE App + Context (#8469) --- .../pageobject/Patient/PatientLogupdate.ts | 6 +- public/locale/en.json | 17 + src/CAREUI/interactive/KeyboardShortcut.tsx | 29 +- src/Routers/AppRouter.tsx | 5 +- src/Utils/request/api.tsx | 31 - src/Utils/useSegmentedRecorder.ts | 16 +- src/Utils/useValueInjectionObserver.tsx | 55 ++ src/Utils/useVoiceRecorder.ts | 158 ++++ .../Common/BloodPressureFormField.tsx | 4 +- src/components/Common/DateInputV2.tsx | 20 +- .../Common/TemperatureFormField.tsx | 24 +- .../PrescriptionDropdown.tsx | 2 +- src/components/Files/AudioCaptureDialog.tsx | 7 +- .../Form/FormFields/Autocomplete.tsx | 20 +- .../FormFields/AutocompleteMultiselect.tsx | 26 +- .../Form/FormFields/RadioFormField.tsx | 2 +- .../FormFields/RangeAutocompleteFormField.tsx | 2 +- .../Form/FormFields/TextFormField.tsx | 51 +- src/components/Form/SelectMenuV2.tsx | 26 +- src/components/Patient/DailyRounds.tsx | 152 ++-- src/components/Scribe/Scribe.tsx | 745 ------------------ src/components/Scribe/formDetails.ts | 384 --------- src/components/Symptoms/SymptomsBuilder.tsx | 43 +- src/hooks/useRecorder.d.ts | 3 - src/hooks/useRecorder.js | 86 -- src/pluginTypes.ts | 2 + tailwind.config.js | 6 +- 27 files changed, 507 insertions(+), 1415 deletions(-) create mode 100644 src/Utils/useValueInjectionObserver.tsx create mode 100644 src/Utils/useVoiceRecorder.ts delete mode 100644 src/components/Scribe/Scribe.tsx delete mode 100644 src/components/Scribe/formDetails.ts delete mode 100644 src/hooks/useRecorder.d.ts delete mode 100644 src/hooks/useRecorder.js diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index d7b49fde05e..68287bfae41 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -51,7 +51,7 @@ class PatientLogupdate { } typePulse(pulse: string) { - cy.typeAndSelectOption("#pulse", pulse); + cy.get("#pulse").click().type(pulse); } typeTemperature(temperature: string) { @@ -59,11 +59,11 @@ class PatientLogupdate { } typeRespiratory(respiratory: string) { - cy.typeAndSelectOption("#resp", respiratory); + cy.get("#resp").click().type(respiratory); } typeSpo2(spo: string) { - cy.typeAndSelectOption("#ventilator_spo2", spo); + cy.get("#ventilator_spo2").click().type(spo); } selectRhythm(rhythm: string) { diff --git a/public/locale/en.json b/public/locale/en.json index 1159c4ab0dc..2e8469a3667 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -262,6 +262,8 @@ "abha_number_exists_description": "There is an ABHA Number already linked with the given Aadhaar Number, Do you want to create a new ABHA Address?", "abha_number_linked_successfully": "ABHA Number has been linked successfully.", "abha_profile": "ABHA Profile", + "accept": "Accept", + "accept_all": "Accept All", "access_level": "Access Level", "action_irreversible": "This action is irreversible", "active": "Active", @@ -342,6 +344,7 @@ "auth_method_unsupported": "This authentication method is not supported, please try a different method", "authorize_shift_delete": "Authorize shift delete", "auto_generated_for_care": "Auto Generated for Care", + "autofilled_fields": "Autofilled Fields", "available_features": "Available Features", "available_in": "Available in", "average_weekly_working_hours": "Average weekly working hours", @@ -500,6 +503,8 @@ "continue_watching": "Continue watching", "contribute_github": "Contribute on Github", "copied_to_clipboard": "Copied to clipboard", + "copilot_thinking": "Copilot is thinking...", + "could_not_autofill": "We could not autofill any fields from what you said", "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", @@ -722,6 +727,7 @@ "health_facility__registered_2": "No Action Required", "health_facility__registered_3": "Registered", "health_facility__validation__hf_id_required": "Health Facility Id is required", + "hearing": "We are hearing you...", "help_confirmed": "There is sufficient diagnostic and/or clinical evidence to treat this as a confirmed condition.", "help_differential": "One of a set of potential (and typically mutually exclusive) diagnoses asserted to further guide the diagnostic process and preliminary treatment.", "help_entered-in-error": "The statement was entered in error and is not valid.", @@ -1041,6 +1047,7 @@ "prn_prescriptions": "PRN Prescriptions", "procedure_suggestions": "Procedure Suggestions", "procedures_select_placeholder": "Select procedures to add details", + "process_transcript": "Process Again", "profile": "Profile", "provisional": "Provisional", "qualification": "Qualification", @@ -1068,6 +1075,7 @@ "refuted": "Refuted", "register_hospital": "Register Hospital", "register_page_title": "Register As Hospital Administrator", + "reject": "Reject", "reload": "Reload", "remove": "Remove", "rename": "Rename", @@ -1097,6 +1105,7 @@ "result_on": "Result on", "resume": "Resume", "retake": "Retake", + "retake_recording": "Retake Recording", "return_to_care": "Return to CARE", "return_to_login": "Return to Login", "return_to_password_reset": "Return to Password Reset", @@ -1117,6 +1126,8 @@ "save_and_continue": "Save and Continue", "save_investigation": "Save Investigation", "scan_asset_qr": "Scan Asset QR!", + "scribe__reviewing_field": "Reviewing field {{currentField}} / {{totalFields}}", + "scribe_error": "Could not autofill fields", "search_by_username": "Search by username", "search_for_facility": "Search for Facility", "search_icd11_placeholder": "Search for ICD-11 Diagnoses", @@ -1181,9 +1192,11 @@ "staff_list": "Staff List", "start_datetime": "Start Date/Time", "start_dosage": "Start Dosage", + "start_review": "Start Review", "state": "State", "status": "Status", "stop": "Stop", + "stop_recording": "Stop Recording", "stream_stop_due_to_inativity": "The live feed will stop streaming due to inactivity", "stream_stopped_due_to_inativity": "The live feed has stopped streaming due to inactivity", "stream_uuid": "Stream UUID", @@ -1200,6 +1213,7 @@ "support": "Support", "switch": "Switch", "switch_camera_is_not_available": "Switch camera is not available.", + "symptoms": "Symptoms", "systolic": "Systolic", "tachycardia": "Tachycardia", "target_dosage": "Target Dosage", @@ -1212,6 +1226,8 @@ "total_beds": "Total Beds", "total_staff": "Total Staff", "total_users": "Total Users", + "transcript_edit_info": "You can update this if we made an error", + "transcript_information": "This is what we heard", "transfer_in_progress": "TRANSFER IN PROGRESS", "transfer_to_receiving_facility": "Transfer to receiving facility", "travel_within_last_28_days": "Domestic/international Travel (within last 28 days)", @@ -1301,6 +1317,7 @@ "vitals": "Vitals", "vitals_monitor": "Vitals Monitor", "vitals_present": "Vitals Monitor present", + "voice_autofill": "Voice Autofill", "ward": "Ward", "warranty_amc_expiry": "Warranty / AMC Expiry", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", diff --git a/src/CAREUI/interactive/KeyboardShortcut.tsx b/src/CAREUI/interactive/KeyboardShortcut.tsx index 07f3af6a429..ab825d4aada 100644 --- a/src/CAREUI/interactive/KeyboardShortcut.tsx +++ b/src/CAREUI/interactive/KeyboardShortcut.tsx @@ -1,6 +1,7 @@ +import { Fragment } from "react/jsx-runtime"; import useKeyboardShortcut from "use-keyboard-shortcut"; -import { classNames, isAppleDevice } from "@/Utils/utils"; +import { classNames, isAppleDevice } from "../../Utils/utils"; interface Props { children?: React.ReactNode; @@ -70,3 +71,29 @@ const SHORTCUT_KEY_MAP = { ArrowLeft: "←", ArrowRight: "→", } as Record; + +export function KeyboardShortcutKey(props: { + shortcut: string[]; + useShortKeys?: boolean; +}) { + const { shortcut, useShortKeys } = props; + + return ( +
+ {shortcut.map((key, idx, keys) => ( + + + {SHORTCUT_KEY_MAP[key] + ? useShortKeys + ? SHORTCUT_KEY_MAP[key][0] + : SHORTCUT_KEY_MAP[key] + : key} + + {idx !== keys.length - 1 && ( + + + )} + + ))} +
+ ); +} diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 686094c9be1..125ee8c9055 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -158,7 +158,10 @@ export default function AppRouter() { id="pages" className="flex-1 overflow-y-scroll bg-gray-100 pb-4 focus:outline-none md:py-0" > -
+
{pages}
diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index d6d30dfce85..40021007773 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -81,7 +81,6 @@ import { CreateFileResponse, FileUploadModel, } from "@/components/Patient/models"; -import { ScribeModel } from "@/components/Scribe/Scribe"; import { SkillModel, SkillObjectModel, @@ -111,36 +110,6 @@ export interface LoginCredentials { } const routes = { - createScribe: { - path: "/api/care_scribe/scribe/", - method: "POST", - TReq: Type(), - TRes: Type(), - }, - getScribe: { - path: "/api/care_scribe/scribe/{external_id}/", - method: "GET", - TRes: Type(), - }, - updateScribe: { - path: "/api/care_scribe/scribe/{external_id}/", - method: "PUT", - TReq: Type(), - TRes: Type(), - }, - createScribeFileUpload: { - path: "/api/care_scribe/scribe_file/", - method: "POST", - TBody: Type(), - TRes: Type(), - }, - editScribeFileUpload: { - path: "/api/care_scribe/scribe_file/{id}/?file_type={fileType}&associating_id={associatingId}", - method: "PATCH", - TBody: Type>(), - TRes: Type(), - }, - // Auth Endpoints login: { path: "/api/v1/auth/login/", diff --git a/src/Utils/useSegmentedRecorder.ts b/src/Utils/useSegmentedRecorder.ts index c10379a9d12..806e80064a2 100644 --- a/src/Utils/useSegmentedRecorder.ts +++ b/src/Utils/useSegmentedRecorder.ts @@ -8,6 +8,7 @@ const useSegmentedRecording = () => { const [recorder, setRecorder] = useState(null); const [audioBlobs, setAudioBlobs] = useState([]); const [restart, setRestart] = useState(false); + const [microphoneAccess, setMicrophoneAccess] = useState(false); // New state const { t } = useTranslation(); const bufferInterval = 1 * 1000; @@ -25,6 +26,7 @@ const useSegmentedRecording = () => { requestRecorder().then( (newRecorder) => { setRecorder(newRecorder); + setMicrophoneAccess(true); // Set access to true on success if (restart) { setIsRecording(true); } @@ -34,6 +36,7 @@ const useSegmentedRecording = () => { msg: t("audio__permission_message"), }); setIsRecording(false); + setMicrophoneAccess(false); // Set access to false on failure }, ); } @@ -98,8 +101,16 @@ const useSegmentedRecording = () => { return () => recorder.removeEventListener("dataavailable", handleData); }, [recorder, isRecording, bufferInterval, audioBlobs, restart]); - const startRecording = () => { - setIsRecording(true); + const startRecording = async () => { + try { + const newRecorder = await requestRecorder(); + setRecorder(newRecorder); + setMicrophoneAccess(true); + setIsRecording(true); + } catch (error) { + setMicrophoneAccess(false); + throw new Error("Microphone access denied"); + } }; const stopRecording = () => { @@ -116,6 +127,7 @@ const useSegmentedRecording = () => { stopRecording, resetRecording, audioBlobs, + microphoneAccess, // Return microphoneAccess }; }; diff --git a/src/Utils/useValueInjectionObserver.tsx b/src/Utils/useValueInjectionObserver.tsx new file mode 100644 index 00000000000..ee1530bd3a0 --- /dev/null +++ b/src/Utils/useValueInjectionObserver.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from "react"; + +/** + * A custom React hook that observes changes to a specific attribute of a DOM element + * and returns a new value when the attribute changes. It is primarily useful + * for detecting updates to the value of a custom CUI component through layers where + * the component's state cannot be determined or mutated (example. CARE Scribe). + * + * @template T + * @param {Object} options - Configuration options for the observer. + * @param {HTMLElement | null} options.targetElement - The DOM element to observe for attribute changes. + * @param {string} [options.attribute="value"] - The name of the attribute to observe (default is "value"). + * + * @example + * const targetElement = document.getElementById('my-input'); + * const DOMValue = useValueInjectionObserver({ + * targetElement: targetElement, + * attribute: 'value', + * }); + * + * @returns {unknown} This hook returns the current value of the attribute. + */ +export function useValueInjectionObserver(options: { + targetElement: HTMLElement | null; + attribute?: string; +}) { + const { targetElement, attribute = "value" } = options; + const [value, setValue] = useState(); + + useEffect(() => { + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + if ( + mutation.type === "attributes" && + mutation.attributeName === attribute + ) { + const newValue = JSON.parse( + targetElement?.getAttribute(attribute) || '""', + ); + setValue(newValue); + } + }); + }); + + const config = { + attributes: true, + attributeFilter: [attribute], + }; + + targetElement && observer.observe(targetElement, config); + return () => observer.disconnect(); + }, [targetElement]); + + return value; +} diff --git a/src/Utils/useVoiceRecorder.ts b/src/Utils/useVoiceRecorder.ts new file mode 100644 index 00000000000..119a5b24bf5 --- /dev/null +++ b/src/Utils/useVoiceRecorder.ts @@ -0,0 +1,158 @@ +import { useEffect, useState } from "react"; + +import * as Notify from "./Notifications"; + +const useVoiceRecorder = (handleMicPermission: (allowed: boolean) => void) => { + const [audioURL, setAudioURL] = useState(""); + const [isRecording, setIsRecording] = useState(false); + const [recorder, setRecorder] = useState(null); + const [blob, setBlob] = useState(null); + const [waveform, setWaveform] = useState([]); // Decibel waveform + + let audioContext: AudioContext | null = null; + let analyser: AnalyserNode | null = null; + let source: MediaStreamAudioSourceNode | null = null; + + useEffect(() => { + if (!isRecording && recorder && audioURL) { + setRecorder(null); + } + }, [isRecording, recorder, audioURL]); + + useEffect(() => { + const initializeRecorder = async () => { + try { + const fetchedRecorder = await requestRecorder(); + setRecorder(fetchedRecorder); + handleMicPermission(true); + } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : "Please grant microphone permission to record audio."; + Notify.Error({ + msg: errorMessage, + }); + setIsRecording(false); + handleMicPermission(false); + } + }; + // Lazily obtain recorder the first time we are recording. + if (recorder === null) { + if (isRecording) { + initializeRecorder(); + } + return; + } + + if (isRecording) { + recorder.start(); + setupAudioAnalyser(); + } else { + recorder.stream.getTracks().forEach((i) => i.stop()); + recorder.stop(); + if (audioContext) { + audioContext.close(); + } + } + + const handleData = (e: BlobEvent) => { + const url = URL.createObjectURL(e.data); + setAudioURL(url); + const blob = new Blob([e.data], { type: "audio/mpeg" }); + setBlob(blob); + }; + + recorder.addEventListener("dataavailable", handleData); + return () => { + recorder.removeEventListener("dataavailable", handleData); + if (audioContext) { + audioContext.close(); + } + }; + }, [recorder, isRecording]); + + const setupAudioAnalyser = () => { + let animationFrameId: number; + audioContext = new (window.AudioContext || + (window as any).webkitAudioContext)(); + analyser = audioContext.createAnalyser(); + analyser.fftSize = 32; + const bufferLength = analyser.frequencyBinCount; + const dataArray = new Uint8Array(bufferLength); + + source = audioContext.createMediaStreamSource( + recorder?.stream as MediaStream, + ); + source.connect(analyser); + + const updateWaveform = () => { + if (isRecording) { + analyser?.getByteFrequencyData(dataArray); + const normalizedWaveform = Array.from(dataArray).map((value) => + Math.min(100, (value / 255) * 100), + ); + setWaveform(normalizedWaveform); + animationFrameId = requestAnimationFrame(updateWaveform); + } else { + cancelAnimationFrame(animationFrameId); + source?.disconnect(); + analyser?.disconnect(); + } + }; + + updateWaveform(); + }; + + const startRecording = () => { + setIsRecording(true); + }; + + const stopRecording = () => { + setIsRecording(false); + setWaveform([]); + }; + + const resetRecording = () => { + setAudioURL(""); + setBlob(null); + setWaveform([]); + }; + + return { + audioURL, + isRecording, + startRecording, + stopRecording, + blob, + waveform, + resetRecording, + }; +}; + +async function requestRecorder() { + const constraints: MediaStreamConstraints = { + audio: { + echoCancellation: true, + noiseSuppression: true, + // iOS Safari requires these constraints + sampleRate: 44100, + channelCount: 1, + }, + }; + try { + const stream = await navigator.mediaDevices.getUserMedia(constraints); + // iOS Safari requires a different mime type + const options = { + mimeType: MediaRecorder.isTypeSupported("audio/webm") + ? "audio/webm" + : "audio/mp4", + }; + return new MediaRecorder(stream, options); + } catch (error) { + throw new Error( + `Failed to initialize recorder: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } +} +export default useVoiceRecorder; diff --git a/src/components/Common/BloodPressureFormField.tsx b/src/components/Common/BloodPressureFormField.tsx index 77ef37fe5d7..2b928029d3d 100644 --- a/src/components/Common/BloodPressureFormField.tsx +++ b/src/components/Common/BloodPressureFormField.tsx @@ -49,7 +49,9 @@ export default function BloodPressureFormField(props: Props) { labelClassName="hidden" errorClassName="hidden" /> - / + + / + = ({ const minutes = dayjs(value).minute(); const ampm = dayjs(value).hour() > 11 ? "PM" : "AM"; + const dateInputRef = useRef(null); const hourScrollerRef = useRef(null); const minuteScrollerRef = useRef(null); @@ -289,10 +291,25 @@ const DateInputV2: React.FC = ({ return `${right ? "md:-translate-x-1/2" : ""} ${top ? "md:-translate-y-[calc(100%+50px)]" : ""}`; }; + const domValue = useValueInjectionObserver({ + targetElement: dateInputRef.current, + attribute: "data-cui-dateinput-value", + }); + + useEffect(() => { + if (value !== domValue && typeof domValue !== "undefined") + onChange(dayjs(domValue).toDate()); + }, [domValue]); + return (
{({ open, close }) => { @@ -304,13 +321,14 @@ const DateInputV2: React.FC = ({ className="w-full" ref={popoverButtonRef} > - + ) => { const newValue = e.value; + setInputValue(newValue); - const regex = /^-?\d*\.?\d{0,1}$/; - if (regex.test(newValue)) { - setInputValue(newValue); - } - }; - - const handleBlur = () => { - if (!inputValue) return; - const parsedValue = parseFloat(inputValue); - if (isNaN(parsedValue)) return; + if (Number.isNaN(newValue)) return; - const finalValue = - unit === "celsius" - ? celsiusToFahrenheit(parsedValue).toString() - : parsedValue.toString(); + const valueInFahrenheit = + unit === "celsius" ? celsiusToFahrenheit(Number(newValue)) : newValue; - setInputValue(finalValue); - onChange({ name, value: finalValue }); + onChange({ name, value: valueInFahrenheit.toString() }); }; return ( @@ -82,7 +71,6 @@ export default function TemperatureFormField({ max={`${unit === "celsius" ? 41.1 : 106}`} step={0.1} onChange={handleInputChange} - onBlur={handleBlur} autoComplete="off" error={error} /> diff --git a/src/components/Common/prescription-builder/PrescriptionDropdown.tsx b/src/components/Common/prescription-builder/PrescriptionDropdown.tsx index 901fe1db9d6..1a726e5fd85 100644 --- a/src/components/Common/prescription-builder/PrescriptionDropdown.tsx +++ b/src/components/Common/prescription-builder/PrescriptionDropdown.tsx @@ -59,7 +59,7 @@ export function PrescriptionDropdown(props: { > {options.map((option, i) => { return ( -
+
- ); - case "recording": - return ( - - ); - case "submit": - return

Processing...

; - default: - return null; - } - }; - - function getStageMessage(stage: string) { - if (errors?.length > 0) return "Errored out. Please try again."; - if (isGPTProcessing) return "Extracting form data from transcript..."; - if (isAudioUploading) return "Uploading audio..."; - if (isTranscribing) return "Transcribing audio..."; - switch (stage) { - case "start": - return "Click the microphone to start recording"; - case "recording": - return "Recording..."; - case "recording-review": - return "Uploading audio..."; - case "review": - return "Transcript generated"; - case "submit": - return "Generating form data from transcript..."; - case "final-review": - return "Form data extracted. Please review."; - default: - return ""; - } - } - - if (!featureFlags.includes("SCRIBE_ENABLED")) return null; - - return ( - - - setOpen(!open)} - className="rounded py-2 font-bold" - > - - Voice AutoFill - - - {open && ( - -
- - -
-
-

- Voice AutoFill -

- - { - handleRerecordClick(); - setOpen(false); - }} - className={`flex items-center justify-center rounded-full bg-white p-2 font-bold text-red-500 transition duration-150 ease-in-out hover:bg-red-200 ${ - stage === "start" && "opacity-0 hover:cursor-default" - }`} - > - - -
- - {(stage === "review" || - stage === "final-review" || - stage === "recording-review") && ( -
-

- Recorded Audio -

- {audioBlobs.length > 0 && - audioBlobs.map((blob, index) => ( -
- )} - - {(stage === "review" || stage === "final-review") && ( -
-
-

- Transcript -

-

- (Edit if needed) -

-
-